我很惊讶这个问题没有出现在我的搜索结果中,考虑到在C++11中移动语义的实用性,我认为某个人以前应该已经问过了:
在何时需要(或者说是一个好主意)将类设为不可移动的?
(除了与现有代码兼容性等原因之外。)
我很惊讶这个问题没有出现在我的搜索结果中,考虑到在C++11中移动语义的实用性,我认为某个人以前应该已经问过了:
(除了与现有代码兼容性等原因之外。)
Herb 的回答(在被编辑之前)实际上给出了一个不应该被移动的类型的好例子:std::mutex
。
操作系统的本地互斥锁类型(例如,在 POSIX 平台上是 pthread_mutex_t
)可能不是“位置不变”的,这意味着对象的地址是其值的一部分。例如,操作系统可能会保留指向所有已初始化互斥锁对象的指针列表。如果 std::mutex
包含作为数据成员的本地操作系统互斥锁类型,并且本地类型的地址必须保持固定(因为操作系统维护其互斥锁的指针列表),则要么 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
的内部数据拆分出来,只能复制其值并将其设置为0,但它仍然是一个具有值的int
,只是内存中的字节而已。但在语言术语中,int
仍然是“可移动”的,因为复制是一种有效的移动操作。 对于不可复制的类型,如果您不想或不能移动内存块,并且也无法复制其值,则它是不可移动的。互斥锁或原子变量是特定的内存位置(具有特殊属性),因此移动它是没有意义的,并且也不可复制,因此它是不可移动的。
std::unique_ptr
)是可移动的,但复制它们没有意义;这些是自然的“只移动”类型。int
或 vector<widget>
这样的值类型。它们代表值,应当可以复制。在 C++11 中,通常应将移动视为复制的优化,因此所有可复制的类型自然应当也是可移动的,移动仅仅是一种在通常情况下你不再需要原始对象并且打算销毁它时,进行拷贝的高效方式。base*
或 base&
,因此不提供复制构造以避免切片;如果您确实想获得与现有对象完全相同的另一个对象,则通常调用像clone
这样的虚函数。这些不需要移动构造或赋值有两个原因:它们不可复制,并且它们已经有了更有效的自然“移动”操作——您只需复制/移动指向对象的指针,对象本身根本不必移动到新的内存位置。std::unique_ptr
)是自然的“仅限移动”类型,因为它们不像值类型(复制它们没有意义),但你会直接使用它们(并非始终通过指针或引用),因此希望将这些类型的对象从一个地方移动到另一个地方。std::mutex
是不可移动的,因为 POSIX 互斥锁是使用地址来操作的。 - Puppy实际上,当我进行搜索时,我发现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
std::move_iterator
(无论如何,它有完全不同的目的)。同样,std::time_point
和std::duration
(以及可能还有其他我没有仔细检查/思考的类型)也是如此。 - Christian Rauios_base
,type_info
,facet
),3. 各种奇怪的东西(sentry
)。
可能普通程序员唯一编写的无法移动的类属于第二类。 - Philippclass 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);
}
}
T x = std::move(anotherT);
是合法的”并不等同。后者是一个移动请求,如果T没有移动构造函数,则可能会退回到复制构造函数。那么,“可移动”到底意味着什么? - sellibitze