摘要:
对于C++11,我会包括以下内容:
noexcept
)noexcept
)operator<()
实现自然全序关系,如果不存在自然全序关系,则使用 std::less<>
)。hash<>
并且会删除以下内容:
swap()
(不抛出异常)- 由移动操作替代。评论
Alex 在 Elements of Programming 中重新审视了正则类型的概念。实际上,该书的大部分内容都是关于正则类型的。
一组程序的包含,使我们能够将对象放置在数据结构中并使用算法将对象从一个数据结构复制到另一个数据结构,这样的类型被称为正则类型,因为它们的使用保证了行为的规律性和互操作性。 -- EoP第1.5节
在EoP中,Alex引入了底层类型的概念,它给我们提供了一个非抛出的交换算法,可用于移动。在C++中,底层类型模板无法以任何特别有用的方式实现,但是您可以使用非抛出 (noexcept
) 移动构造函数和移动赋值作为合理的近似值(底层类型允许将临时变量移动/复制到其他地方而不需要销毁)。在C++03中,如果提供了非抛出的swap()
,则建议使用它来近似移动操作,如果提供了移动构造函数和移动赋值,则默认的std::swap()
就足够了(尽管您仍然可以实现更有效的方法)。
[ 我已经公开发表过建议使用单一赋值运算符,通过值传递来涵盖移动赋值和复制赋值。不幸的是,当前语言规则中当类型获得默认移动构造函数时会导致复合类型出现问题。在语言中修复这个问题之前,您需要编写两个赋值运算符。然而,您仍然可以使用按值传递其他汇聚参数以避免处理所有参数的移动/复制的组合问题。 ]
Alex 还添加了完全排序的要求(尽管可能没有自然的完全排序,排序可能纯粹是表示性的)。operator<()
应该保留给自然的完全排序。如果自然的完全排序不可用,则建议专门化 std::less<>()
(标准中也有先例)。
在 EoP 中,Alex 放宽了对相等性的要求,以允许表示相等性足够。这是一个有用的改进。
一个普通类型也应该是等式完备的(即,operator==()
应该可以作为非友元、非成员函数实现)。一个等式完备的类型也是可序列化的(虽然没有规范的序列化格式,但实现流运算符除了调试外几乎没什么用处)。一个等式完备的类型也可以被散列。在 C++11 中(或使用 TR1),您应该提供 std::hash
的特化。area()
,目前还没有任何标准语法 - 实际上除了测试之外可能很少有理由实现它。它是一个用于指定复杂度的有用概念 - 我经常为测试复杂度实现它(或其近似值)。例如,我们将复制的复杂度定义为对象区域复制的时间限制。泛型编程的限制最好用表达式来说明。对于复制性的相同限制的更现代版本是,以下两个语句都应该是有效的:
T b = a;
并且
T b = ra;
其中a
是具有类型T
或const T
的lvalue,ra
是具有类型T
或const T
的rvalue。(后置条件类似。)
我认为这个表述符合论文的精神。请注意,C++03已经使用了像lvalues和rvalues这样的概念,这意味着我们所表达的约束需要T source(); T b = source();
之类的代码是有效的 - 这显然是很合理的。
在这些约束条件下,C++11没有太多变化。特别值得注意的是,这样的(病态)类型是不规则的:
struct irregular {
irregular() = default;
irregular(irregular const&) = default;
irregular& operator=(irregular const&) = default;
irregular(irregular&&) = delete;
irregular& operator=(irregular&&) = delete;
};
由于类似 irregular a; irregular b = a;
是有效的,而 irregular source(); irregular b = source();
却不是。这是一种有点可复制(或可复制赋值)但还不够的类型。[这被认为是某种缺陷,并计划在C++1y中进行更改,使得这样的类型实际上是可复制的。]
进一步地,对于拷贝后条件必须在某种意义上等同于原始对象(或者对于右值,等同于拷贝之前的原始对象)的情况,移动特殊成员只能是相应拷贝特殊成员的“优化”。换句话说,拷贝语义是移动语义的细化。这意味着以下断言必须成立:
T a;
T b = a;
T c = std::move(a);
assert( b == c );
即使我们是通过复制“请求”(即涉及左值源的表达式)或移动请求(涉及右值源的表达式)到达那里的,我们必须有相同的结果,不管实际发生了什么(是否涉及复制特殊成员或移动特殊成员)。
有趣的是,诸如std::is_copy_constructible
之类的特性曾经被称为std::has_copy_constructor
,但被重新命名以强调表达式而不是固有属性。例如,像std::is_copy_constructible<int>::value && std::is_move_assignable<int>::value
这样的东西是真实的,无论int
是否具有构造函数或赋值运算符。
我建议您通过在表达式级别上表示约束来真正进行通用编程,因为例如,移动构造函数的存在或不存在既不足够也不必要使一种类型可以被复制构造。