令我惊讶的是,它没有出现在我的搜索结果中,考虑到C ++ 11中移动语义的用处,我想有人会问过这个问题:
什么时候需要(或者对我来说是个好主意)使类在C ++ 11中不可移动?
(原因等比现有的代码,也就是兼容性问题。)
T x = std::move(anotherT);
合法”并不等效。后者是一个移动请求,在T没有移动ctor的情况下,它可能会落在复制ctor上。那么,“可移动”到底是什么意思?
令我惊讶的是,它没有出现在我的搜索结果中,考虑到C ++ 11中移动语义的用处,我想有人会问过这个问题:
(原因等比现有的代码,也就是兼容性问题。)
T x = std::move(anotherT);
合法”并不等效。后者是一个移动请求,在T没有移动ctor的情况下,它可能会落在复制ctor上。那么,“可移动”到底是什么意思?
Answers:
Herb的答案(在进行编辑之前)实际上给出了一个不应移动的类型的好例子:std::mutex
。
操作系统的本机互斥类型(例如,pthread_mutex_t
在POSIX平台上)可能不是“位置不变”的,这意味着对象的地址是其值的一部分。例如,操作系统可能会保留指向所有已初始化互斥对象的指针的列表。如果std::mutex
包含本机OS互斥锁类型作为数据成员,并且本机类型的地址必须保持固定(因为OS维护了指向其互斥锁的指针列表),那么任何一个std::mutex
都必须将本机互斥锁类型存储在堆上,因此它将保持在在std::mutex
对象之间std::mutex
移动或不能移动时的相同位置。无法将其存储在堆上,因为a std::mutex
具有constexpr
构造函数,并且必须符合常量初始化(即静态初始化)的条件,以便全局std::mutex
可以确保在程序执行开始之前对其进行构造,因此其构造函数不能使用new
。因此,剩下的唯一选择就是保持std::mutex
不动。
相同的推理适用于包含需要固定地址的其他类型的其他类型。如果资源的地址必须保持固定,请不要移动它!
还有一个关于不移动的论点std::mutex
,那就是安全地执行此操作非常困难,因为您需要知道在移动互斥对象时没有人试图锁定该互斥对象。由于互斥锁是您可以用来防止数据争用的构造块之一,因此,如果互斥量对抵御竞争本身并不安全,那将是不幸的!对于不动产,std::mutex
您知道一旦构造它并且在销毁它之前,任何人都只能对它进行锁定和解锁,并且明确保证这些操作是线程安全的,并且不会引入数据争用。同样的论点也适用于std::atomic<T>
对象:除非可以原子地移动它们,否则无法安全地移动它们,否则另一个线程可能正在尝试调用compare_exchange_strong
现在正在移动物体。因此,类型不应移动的另一种情况是它们是安全并发代码的低级构建块,并且必须确保对它们进行的所有操作都是原子性的。如果可以随时将对象值移动到新对象,则需要使用原子变量来保护每个原子变量,以便知道使用它是安全的还是已经被移动了……以及要保护的原子变量该原子变量,依此类推...
我想我可以概括地说,当一个对象仅仅是一块纯内存,而不是充当值或值抽象的持有人的类型时,移动它就没有意义。基本类型(例如,int
无法移动):移动它们只是一个副本。您无法从中删除胆量int
,可以复制其值,然后将其设置为零,但是它仍然是int
带值的,只是内存的字节数。但是一个int
仍然可以移动用语言来表示,因为复制是有效的移动操作。但是,对于不可复制的类型,如果您不想或无法移动内存,并且也无法复制其值,则它是不可移动的。互斥锁或原子变量是内存的特定位置(使用特殊属性进行处理),因此移动没有任何意义,并且也是不可复制的,因此不可移动。
简短答案:如果类型是可复制的,则它也应该是可移动的。但是,事实并非如此:有些类型std::unique_ptr
是可移动的,但复制它们没有任何意义。这些自然是仅移动类型。
答案略长...
有两种主要类型(在其他一些更特殊用途的类型中,例如特征):
类似值的类型,例如int
或vector<widget>
。这些代表值,自然应该是可复制的。在C ++ 11中,通常您应该将move视为对副本的优化,因此所有可复制类型自然应该是可移动的...在通常不使用的情况下,移动只是一种有效的复制方法不再需要原始物体,并且无论如何将要销毁它。
继承层次结构中存在类似引用的类型,例如基类和具有虚拟或受保护成员函数的类。这些通常由指针或引用(通常为a base*
或)保存base&
,因此不提供复制构造以避免切片;如果您确实想获得另一个像现有对象一样的对象,通常可以调用虚函数clone
。这些不需要进行移动构造或分配,其原因有两个:它们不可复制,并且它们已经具有更有效的自然“移动”操作-您只需复制/移动指向对象的指针,而对象本身不会必须移到新的存储位置。
大多数类型都属于这两种类型之一,但是也有其他类型的类型也有用,只是很少见。特别是在这里,表示资源唯一所有权的类型(例如)std::unique_ptr
自然是只能移动的类型,因为它们不像值一样(复制它们没有意义),但是您确实可以直接使用它们(并非总是如此)通过指针或引用),因此希望将这种类型的对象从一个地方移动到另一个地方。
std::mutex
是不可移动的,因为POSIX互斥体由地址使用。
实际上,当我四处搜索时,我发现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
iterators / iterator adaptors
应该将其删除,因为C ++ 11具有move_iterator?
std::reference_wrapper
。好的,其他似乎确实是不可移动的。
ios_base
,type_info
,facet
),3。什锦奇怪的东西(sentry
)。通常,程序员只能编写的唯一不可移动的类是第二类。
我发现的另一个原因-性能。假设您有一个拥有价值的“ 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()
{
a a;
{
auto guard = a.changeValue(2);
}
}
如果我将change_value_guard设置为可移动,则必须在其析构函数中添加一个“ if”,以检查后卫是否已移出-这是一个额外的if和性能影响。
是的,可以肯定,可以通过任何理智的优化器对其进行优化,但是仍然很高兴该语言(尽管这需要C ++ 17,才能返回不可移动的类型,但需要保证复制省略),不需要我们如果我们不打算从创建函数返回警卫(除了不付任何使用费的原则)之外退还警卫,那就要付出代价。