成本与收益
“没有零成本的抽象” (https://www.youtube.com/watch?v=rHIkrotSwcc) 是一场很好的 CppCon 演讲。它告诉我们要关注成本和收益。对于 C++ 核心准则中的月份抽象,我认为以下是成本和收益。你可以选择是否认为收益大于成本:
```cpp
#include <iostream>
// 准则 P1 是关于在代码中直接表达思想。其中一部分是使用用户定义的类型,这些类型比 int 更好地表达一个想法。
// 本文件以 P1 中的日期/月份示例为基础进行了扩展。
// 中性 1:尽管封装了无符号整数,但并没有变得更慢。
struct CalendarType
{
// 中性 2:用户不知道值是基于 0 还是 1。
unsigned int value;
// 成本 1:用户要么必须使用 month.value,要么我们必须为所需的方法编写样板代码。
// 缓解 1:C++ 20 中比较运算符的样板代码只需几行。
bool operator==(const CalendarType &other) const = default;
std::strong_ordering operator<=>(const CalendarType &other) const = default;
};
// 成本 2:我们需要编写一些样板代码。
// 缓解 2:我们已将公共代码放入基类中。
struct Year : CalendarType
{
explicit Year(int year) : CalendarType(year) {}
};
struct Month : public CalendarType
{
explicit Month(int month) : CalendarType(month) {}
};
struct Day : public CalendarType
{
explicit Day(int day) : CalendarType(day) {}
};
class Date
{
public:
Date(Year year, Month month, Day day)
: m_year(year),
m_month(month),
m_day(day)
{
}
Year year() const
{
return m_year;
}
Month month() const
{
return m_month;
}
Day day() const
{
return m_day;
}
private:
// 成本 3:要完全理解,读者需要查看 Year、Month 和 Day 的实现。
Year m_year;
Month m_month;
Day m_day;
};
int main()
{
// 成本 2:
Date date1 {Year(1970), Month(4), Day(7)}; // 收益 1:读者清楚每个参数是什么。
Date date2 {Year(1983), Month(1), Day(12)};
// Date date3 {7, 4, 1979}; // 收益 2:代码编写者无法将它们放错顺序
// (由于显式声明,这段代码不会编译)。
// (是的,我略过了闰年的边界情况)
bool earlierInTheYear = date2.month() < date1.month() ||
(date2.month() == date1.month() && date2.day() < date1.day());
std::cout << "1983-01-12 " << (earlierInTheYear ? "is" : "is not")
<< " earlier in the year than 1970-04-07" << std::endl;
}
```
查看原文
"There are no zero-cost abstractions" (https://www.youtube.com/watch?v=rHIkrotSwcc) is a good CppCon talk. It tells as to look for costs and benefits. For the C++ Core Guideline's Month abstraction here are what I see as the costs and benefits. Your choice whether you feel the benefits outweigh the costs :<p>#include <iostream><p>// Guideline P1 is about expressing ideas directly in code. One part of that is
// about using user defined types that express an idea better than say an int.
// This file takes the Date/Month example in P1 and expands upon it.<p>// Neutral 1 : Despite wrapping the unsigned int it is no slower.<p>struct CalendarType
{
// Neutral 2 : The user does not know if the value is 0 based or 1 based.<p><pre><code> unsigned int value;
// Cost 1 : Either the user has to use say month.value or we have to write boiler plate code for required methods.
// Mitigation 1 : C++ 20 boiler plate for comparison operators is a couple of one liners.
bool operator==(const CalendarType &other) const = default;
std::strong_ordering operator<=>(const CalendarType &other) const = default;</code></pre>
};<p>// Cost 2 : We have a bit of boiler plate code to write.
// Mitigation 2 : We've put the common code into a base class.<p>struct Year : CalendarType
{
explicit Year(int year) : CalendarType(year) {}
};<p>struct Month : public CalendarType
{
explicit Month(int month) : CalendarType(month) {}
};<p>struct Day : public CalendarType
{
explicit Day(int day) : CalendarType(day) {}
};<p>class Date
{
public:
Date(Year year, Month month, Day day)
: m_year(year),
m_month(month),
m_day(day)
{
}<p><pre><code> Year year() const
{
return m_year;
}
Month month() const
{
return m_month;
}
Day day() const
{
return m_day;
}
</code></pre>
private:
// Cost 3 : To fully understand, the reader needs to look at how Year, Month and Day are implemented.<p><pre><code> Year m_year;
Month m_month;
Day m_day;</code></pre>
};<p>int main()
{
// Cost 2 :<p><pre><code> Date date1 {Year(1970), Month(4), Day(7)}; // Benefit 1 : It's clear to the reader what each argument is.
Date date2 {Year(1983), Month(1), Day(12)};
// Date date3 {7, 4, 1979}; // Benefit 2 : Code writer can't get them in the wrong order
// (courtesy of explicit this wont compile).
// (Yes, I've glossed over leap year edge cases)
bool earlierInTheYear = date2.month() < date1.month() ||
date2.month() == date1.month() && date2.day() < date1.day();
std::cout << "1983-01-12 " << (earlierInTheYear ? "is" : "is not")
<< " earlier in the year than 1970-04-07" << std::endl;
}</code></pre>