什么时候在C++ 11中制作一个不可移动的类型?

Meh*_*dad 120 c++ c++-faq move-semantics c++11

我很惊讶这没有显示在我的搜索结果中,我认为有人会在之前问过这个,考虑到C++ 11中移动语义的用处:

我什么时候必须(或者对我来说是个好主意)在C++ 11中创建一个不可移动的类?

(原因比与现有代码的兼容性问题,那是.)

Jon*_*ely 107

赫伯的答案(在编辑之前)实际上给出了一个应该移动的类型的好例子:std::mutex.

操作系统的本机互斥类型(例如,pthread_mutex_t在POSIX平台上)可能不是"位置不变",这意味着对象的地址是其值的一部分.例如,操作系统可能会保留一个指向所有已初始化的互斥对象的指针列表.如果std::mutex包含本机OS互斥类型作为数据成员并且本机类型的地址必须保持固定(因为操作系统维护指向其互斥锁的指针列表),则要么std::mutex必须将本机互斥锁类型存储在堆上,以便它保留在在std::mutex对象之间移动时的相同位置或std::mutex不得移动.将它存储在堆上是不可能的,因为它std::mutex有一个constexpr构造函数,并且必须有资格进行常量初始化(即静态初始化),以便std::mutex保证在程序执行开始之前构造全局,因此它的构造函数不能使用new.所以剩下的唯一选择就是std::mutex不动产.

相同的推理适用于包含需要固定地址的其他类型的其他类型.如果资源的地址必须保持固定,请不要移动它!

还有另一个不动的论点std::mutex是,安全地执行它会非常困难,因为你需要知道没有人试图在移动时锁定互斥锁.由于互斥体是你可以用来防止数据竞争的构建块之一,如果它们不能安全地对抗种族本身就很不幸!对于一个不可移动的std::mutex你知道任何人一旦构造它并且在它被销毁之前可以对它做的唯一的事情是锁定它并解锁它,并且这些操作明确保证是线程安全的并且不引入数据竞争.这个相同的参数适用于std::atomic<T>对象:除非它们可以原子方式移动,否则不可能安全地移动它们,另一个线程可能正试图compare_exchange_strong在移动它的那一刻调用该对象.因此,类型不应该是可移动的另一种情况是它们是安全并发代码的低级构建块,并且必须确保它们上的所有操作的原子性.如果对象值可能在任何时候被移动到一个新对象,你需要使用一个原子变量来保护每个原子变量,这样你就知道它是否可以安全使用它或者它已被移动......和一个原子变量来保护那个原子变量,等等......

我想我会概括地说,当一个对象只是一个纯粹的内存片段,而不是一个作为值或值的抽象的持有者的类型时,移动它是没有意义的.基本类型如int无法移动:移动它们只是一个副本.你不能撕掉它的内容int,你可以复制它的值,然后将它设置为零,但它仍然是int一个值,它只是内存的字节.但是int仍然可以在语言术语中移动,因为副本是有效的移动操作.但是,对于不可复制的类型,如果您不想或不能移动内存并且您也无法复制其值,那么它是不可移动的.互斥锁或原子变量是内存的特定位置(使用特殊属性处理)因此移动没有意义,也不可复制,因此它是不可移动的.

  • +1由于具有特殊地址而无法移动的不太奇特的示例+1是有向图结构中的节点. (16认同)
  • @ tr3w,你不能,除非你在堆上创建互斥锁并通过unique_ptr或类似的方式保存它 (3认同)
  • @BenVoigt,但新对象将拥有自己的互斥锁.我认为他的意思是使用用户定义的移动操作来移动除互斥锁成员之外的所有成员.那么如果旧物体到期怎么办?它的互斥体随之而来. (3认同)
  • 如果互斥锁是不可复制和不可移动的,我如何复制或移动包含互斥锁的对象?(就像一个线程安全类,它有自己的同步互斥...) (2认同)
  • @ tr3w:除了*互斥部分外,你不会只移动整个类*吗? (2认同)
  • @BenVoigt:我看到它的方式,互斥部分是对象的"容器"(即存储位置)的属性,而不是对象本身.所以只要容器在那里它就应该坚持下去,而不是只要物体在那里. (2认同)

Her*_*ter 57

简短回答:如果类型是可复制的,它也应该是可移动的.然而,相反的情况并非如此:某些类型std::unique_ptr是可移动的,但复制它们没有意义; 这些只是自然移动类型.

接下来会有更长的答案......

有两种主要类型(以及其他更特殊的类型,如特征):

  1. 类似值的类型,例如intvector<widget>.这些代表值,自然应该是可复制的.在C++ 11中,通常你应该将move视为复制的优化,因此所有可复制类型都应该是可移动的...移动只是一种有效的方式来复制常见的情况,你不应该不再需要原始物体,无论如何都要破坏它.

  2. 存在于继承层次结构中的类似引用的类型,例如基类和具有虚拟或受保护成员函数的类.这些通常由指针或引用来保存,通常是一个base*或者base&,因此不提供复制结构以避免切片; 如果你想要像现有的那样获得另一个对象,你通常会调用一个虚函数clone.这些不需要移动构造或赋值有两个原因:它们不可复制,并且它们已经具有更高效的自然"移动"操作 - 您只需将指针复制/移动到对象,而对象本身不会必须移动到新的内存位置.

大多数类型属于这两个类别之一,但也有其他类型的类型也很有用,只是更少见.特别是在这里,表示资源的唯一所有权的类型,例如std::unique_ptr,自然是仅移动类型,因为它们不是类似值(复制它们没有意义)但是你直接使用它们(并非总是如此)通过指针或引用)所以想要将这种类型的对象从一个地方移动到另一个地方.

  • 请[真正的Herb Sutter](http://stackoverflow.com/users/297582/herb-sutter)站起来吗?:) (61认同)
  • 这得到了很多赞成,没有人注意到它说当一种类型应该只移动时,这不是问题吗?:) (26认同)
  • @SChepurin:实际上,那就是HerbOverflow. (9认同)
  • 我认为`std :: mutex`是不可移动的,因为地址使用了POSIX互斥锁. (7认同)
  • 是的,我从使用一个OAuth Google帐户切换到另一个帐户,并且无法找到合并这两个登录的方法.(另一个反对OAuth的论点是更引人注目的.)我可能不会再使用另一个了,所以这就是我现在偶尔使用的SO帖子. (6认同)
  • 正如其他人所说,`std :: mutex`不是可移动的,它不是_own_资源,它是一个资源.本机OS互斥体类型可以/应该嵌入到`std :: mutex`对象中,以允许它通过其`constexpr`构造函数进行静态初始化.不涉及资源分配.`std :: thread`会是一个更好的例子. (6认同)
  • @Herb:我标记了您对主持人合并帐户的评论. (3认同)
  • 正如DeadMG所说,`std :: mutex`肯定是错误的例子.`std :: mutex`不能移动也不能复制.`std :: unique_lock`是可移动的可锁定类型. (2认同)
  • 我的downvote代表......这个答案可能还有其他问题.尽管如此,@ Mehrdad肯定会选择乔纳森的答案,而且无论如何都会达到顶峰. (2认同)

bil*_*llz 17

实际上当我搜索时,我发现C++ 11中的一些类型不可移动:

  • 所有mutex类型(recursive_mutex,timed_mutex,recursive_timed_mutex,
  • condition_variable
  • type_info
  • error_category
  • locale::facet
  • random_device
  • seed_seq
  • ios_base
  • basic_istream<charT,traits>::sentry
  • basic_ostream<charT,traits>::sentry
  • 所有atomic类型
  • once_flag

显然有一个关于Clang的讨论:https://groups.google.com/forum/ fromgroups =#!topic / comp.std.c ++/pCO1Qqb3Xa4