什么是移动语义上下文中的“常规类型”?

时间:2012-12-22 00:32:31

标签: c++ generics c++11 move-semantics

Alex Stepanov defined Regular Types作为类型满足复制和相等的某些属性。既然C ++ 11已经将移动语义添加到泛型编程领域,那么Stepanov的定义就不再完整了。我正在寻找对常规类型的一个很好的参考,包括它们与移动语义的交互。

3 个答案:

答案 0 :(得分:44)

<强>要点:

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

  • move-ctor(noexcept
  • move-assign(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()就足够了(尽管你可以仍然实现更有效的一个。)

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

Alex还添加了总排序的要求(尽管可能没有自然的总排序,排序可能纯粹具有代表性)。 operator<()应保留为自然总排序。我的建议是专注std::less<>()如果没有自然的总排序,那么标准中有一些先例。

EoP 中,Alex放宽了对等式的要求,以允许代表性等式充分。一个有用的改进。

常规类型也应该是等价完成的(也就是说,operator==()应该可以作为非朋友,非成员函数实现)。等价完成的类型也是可序列化的(虽然没有规范的序列化格式,但实现流操作符除了调试之外几乎没用)。也可以对类型完全的类型进行哈希处理。在C ++ 11(或TR1)中,您应该提供std::hash的专业化。

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

常规类型的概念不是特定于语言的。我在使用新语言时所做的第一件事就是找出常规类型在该语言中的表现方式。

答案 1 :(得分:10)

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

T b = a;

T b = ra;

其中a是类型为Tconst T的左值,而ra是价值为Tconst 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中进行更改,其中这种类型实际上是可复制的。 ]

更进一步,对于后置条件,副本在某种意义上必须与原始版本(或者,对于rvalues,在复制之前的原始版本)保持一致,移动特殊成员只能是'优化各自的特别成员。另一种说法是复制语义是移动语义的改进。这意味着断言必须包含以下内容:

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没有构造函数或赋值运算符,则为true。

我建议你通过在表达水平上表达约束来真正进行泛型编程,例如移动构造函数的存在与否对于可复制构造的类型来说既不充分也不必要。

答案 2 :(得分:1)

根据Stepanov的论文,

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