何时在C ++ 11中创build一个不可移动的types?

我很惊讶这没有出现在我的search结果中,我以为有人会问这之前,考虑到在C + + 11的移动语义的有用性:

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

(除现有代码的兼容性问题之外的原因,即。)

Herb的答案(编辑之前)实际上给了一个应该移动的types的好例子: std::mutex

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

同样的推理适用于包含需要固定地址的东西的其他types。 如果资源的地址必须保持不变,请不要移动它!

还有一个不移动std::mutex理由,那就是安全地执行它是非常困难的,因为你需要知道没有人在被移动时试图locking互斥锁。 由于互斥体是您可以用来阻止数据竞赛的基石之一,因此如果他们自己对赛事不安全,那将是不幸的! 通过一个不可移动的std::mutex你知道任何人在构造它之前和销毁之前都可以做的唯一的事情是locking它并解锁它,并且这些操作被明确保证是线程安全的,不会引入数据比赛。 这个相同的参数适用于std::atomic<T>对象:除非它们可以自动移动,否则将无法安全地移动它们,另一个线程可能正试图在被移动的对象上调用compare_exchange_strong 。 所以types不能移动的另一种情况是它们是安全并发代码的低级构build块,并且必须确保所有操作的primefaces性。 如果对象的值可能随时移动到一个新的对象上,你需要使用一个primefacesvariables来保护每一个primefacesvariables,这样你就知道它是否可以安全地使用它,或者被移动了…还有一个primefacesvariables来保护那个primefacesvariables,等等…

我想我会概括地说,当一个对象只是一个纯粹的记忆,而不是一个作为价值或抽象价值的持有者的types时,移动它是没有意义的。 int等基本types不能移动:移动它们只是一个副本。 你不能从一个int取出内容,你可以拷贝它的值,然后把它设置为0,但是它仍然是一个带有值的int ,它只是内存的字节数。 但是int在语言中仍然是可移动的 ,因为副本是一个有效的移动操作。 但是,对于不可复制的types,如果你不想或不能移动这块内存,而且你也不能复制它的值,那么它是不可移动的。 一个互斥或一个primefacesvariables是一个特定的内存位置(用特殊属性处理),所以移动并不合理,也不可复制,所以它是不可移动的。

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

稍微长一点的答案如下…

有两种主要types(特别是其他更特殊的types,如特征):

  1. 类似值的types,比如int或者vector<widget> 。 这些代表价值观,自然应该是可以复制的。 在C ++ 11中,通常你应该把移动看作拷贝的优化,所以所有的可拷贝types自然应该是可移动的。在常见的情况下,移动只是一个有效的拷贝方式,不再需要原始的对象,反正只是要摧毁它。

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

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

其实当我四处search的时候,我发现C ++ 11中的一些types是不可移动的:

  • 所有的mutextypes( recursive_mutextimed_mutexrecursive_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
  • 所有atomictypes
  • once_flag

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

我发现的另一个原因 – 性能。 假设你有一个具有价值的类“a”。 您希望输出一个界面,允许用户在有限的时间内更改值(对于范围)。

实现这一点的一个方法是从“a”返回一个“范围守护”对象,该对象将值设置回析构函数,如下所示:

 class a { int value = 0; public: struct change_value_guard { friend a; private: change_value_guard(a& owner, int value) : owner{ owner } { owner.value = value; } change_value_guard(change_value_guard&&) = delete; change_value_guard(const change_value_guard&) = delete; public: ~change_value_guard() { owner.value = 0; } private: a& owner; }; change_value_guard changeValue(int newValue) { return{ *this, newValue }; } }; int main() { aa; { auto guard = a.changeValue(2); } } 

如果我使change_value_guard可移动,那么我必须在析构函数中添加一个“if”来检查警卫是否已经移出 – 这是一个额外的if和性能影响。

是的,当然,它可以被任何理智的优化器优化掉,但是仍然很好,语言(这要求C ++ 17,但是要能够返回一个不可移动的types需要保证副本的省略)并不需要我们支付,如果我们不打算移动卫兵,除了从创buildfunction(不支付你为什么不使用的原则)返回它。