31

Alex Stepanov 将常规类型定义为满足复制和相等的某些属性的类型。现在 C++11 已将移动语义添加到泛型编程领域,Stepanov 的定义不再完整。我正在寻找关于常规类型的良好参考,包括它们与移动语义的交互。

4

3 回答 3

49

概括:

对于 C++11,我将包括:

  • 移动-ctor ( noexcept)
  • 移动赋值 ( noexcept)
  • 全序(operator<()对于自然全序,std::less<>如果自然全序不存在)。
  • hash<>

并会删除:

  • swap()(非投掷) - 由移动操作取代。

评论

Alex 在Elements of Programming中重新审视了常规类型的概念。事实上,本书的大部分内容都是针对常规类型的。

有一组过程包含在类型的计算基础中,使我们可以将对象放置在数据结构中,并使用算法将对象从一个数据结构复制到另一个数据结构。我们称具有这种基础的类型是规则的,因为它们的使用保证了行为的规律性,从而保证了互操作性。-- EoP第 1.5 节

EoP中,Alex 引入了基础类型的概念,它为我们提供了一种可用于移动的非抛出交换算法。在 C++ 中不能以任何特别有用的方式实现一个底层类型模板,但是您可以使用非抛出 ( noexcept) move-ctor 和 move-assign 作为合理的近似值(底层类型允许移入/移出临时对象,而无需额外破坏临时的)。在 C++03 中,提供非抛出swap()是近似移动操作的推荐方法,如果您提供 move-ctor 和 move-assign,那么默认值std::swap()就足够了(尽管您仍然可以实现更有效的方法)。

[ 我在记录中建议您使用单个赋值运算符,按值传递,以涵盖移动分配和复制分配。不幸的是,当一个类型获得一个默认的 move-ctor 时,当前的语言规则会导致它与复合类型中断。在语言中修复之前,您将需要编写两个赋值运算符。但是,您仍然可以对其他接收器参数使用按值传递,以避免在处理所有参数的移动/复制时使用组合。]

Alex 还添加了总排序的要求(尽管可能没有自然的总排序,并且排序可能是纯粹的代表性)。operator<()应该为自然总排序保留。std::less<>()我的建议是,如果自然总排序不可用,则进行专门化,标准中有一些先例)。

EoP中,Alex 放宽了对平等的要求,以允许表示平等就足够了。一个有用的改进。

常规类型也应该是等式完整的(也就是说,operator==()应该可以作为非朋友、非成员函数实现)。等式完整的类型也是可序列化的(尽管没有规范的序列化格式,实现流运算符除了调试之外几乎没有用处)。等式完备的类型也可以被散列。在 C++11(或 TR1)中,您应该提供std::hash.

常规类型的另一个属性area()是还没有任何标准语法 - 除了测试之外,可能没有什么实际实现的理由。对于指定复杂性,这是一个有用的概念——我经常实现它(或近似值)来测试复杂性。例如,我们将复制的复杂度定义为以复制对象区域的时间为界。

常规类型的概念不是特定于语言的。当遇到一门新语言时,我做的第一件事就是弄清楚常规类型在该语言中的表现方式。

于 2012-12-22T04:22:51.410 回答
11

泛型编程的约束最好用表达式来表述。对可复制性的相同约束的更现代的演绎是两个陈述都应该是有效的:

T b = a;

T b = ra;

where是类型为ora的左值,是类型为orT的右值。(具有类似的后置条件。)const TraTconst T

我相信这个提法符合论文的精神。请注意,C++03 已经使用了左值和右值之类的概念,因此我们所表达的约束要求类似的东西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

我建议您通过在表达式级别上表达约束来真正进行泛型编程,因为例如移动构造函数的存在或不存在对于可复制构造的类型来说既不够也没有必要。

于 2012-12-22T03:22:04.237 回答
1

根据 Stepanov 的论文,添加移动赋值和移动复制构造函数,以及所有其他内置类型的运算符,我会说你有它。

于 2012-12-22T00:52:44.693 回答