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

Jef*_*kin 29 c++ generics move-semantics c++11

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

Sea*_*ent 44

摘要:

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

  • move-ctor(noexcept)
  • move-assign(noexcept)
  • 总排序(operator<()对于自然总排序以及std::less<>如果不存在自然总排序).
  • hash<>

并将删除:

  • swap() (非投掷) - 由移动操作代替.

评论

亚历克斯重新审视了编程元素中常规类型的概念.事实上,本书的大部分内容都是针对常规类型的.

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

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

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

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

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

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

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

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

  • (1)表示可能在副本上被切断或无效的关系的对象 - 您可以选择不在这些对象上实现副本,但它们应仅用作构建其他常规类型的机制.(2)等分不完整的对象 - 通常可以通过使对象不可变来实现复制.(3)来自库(平台,OS)的不可复制的类型 - 可复制是传递属性,因此包含此类型的对象不可复制.使用不变性或等式完整性来恢复副本. (4认同)
  • 我更新了链接.是的,即使在C++ 11中,类型也必须是可复制的才能被认为是常规类型,甚至使用资源(图像,容器......)的类型也必须是可复制的.当复制结果无效或被切断时,我们可能选择不为表示关系的类型实现副本<https://github.com/sean-parent/sean-parent.github.com/wiki/presentations /2014-04-14-goal-complete-types/goal-complete-types.pdf>(视频尚未在线).我更喜欢将这些类型称为不完整,而不是"不规则". (2认同)

Luc*_*ton 10

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

T b = a;
Run Code Online (Sandbox Code Playgroud)

T b = ra;
Run Code Online (Sandbox Code Playgroud)

其中a是与类型左值Tconst Tra与类型右值Tconst T.(具有类似的后置条件.)

我相信这种表述符合论文的精神.请注意,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;
};
Run Code Online (Sandbox Code Playgroud)

因为类似的东西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 );
Run Code Online (Sandbox Code Playgroud)

也就是说,我们是否通过复制'请求'(即涉及左值源的表达式)或通过移动请求(涉及右值源的表达式)到达那里,无论"实际"发生了什么,我们必须得到相同的结果(是否涉及复制特别成员或移动特别成员(如果有的话).

令人感兴趣的是,诸如std::is_copy_constructible以前的特征被调用std::has_copy_constructor,但被重命名为强调表达而不是内在属性:std::is_copy_constructible<int>::value && std::is_move_assignable<int>::value无论int没有构造函数或赋值运算符的事实都是如此.

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