C++11的移动语义是否比写时复制技术更高效?

4

我可能有点混淆,但我的理解是:

  • COW在没有任何调用者想要进行修改时返回一个“伪造”副本。
  • 移动语义在所有情况下都会返回一个“伪造”副本。

因此,如果我只需要读取对象而不需要更新它,我应该等待我的C++编译器支持移动语义吗?

我的理解是正确的吗?

2个回答

6
移动语义并不返回一个“假”副本。相反,在复制操作中,它会给被复制的对象(copyee)权限来破坏原始副本。
一个有启发性的例子可能很有教育意义:
std::vector<int> bigData;

// fill bigData

std::vector<std::vector<int> > listOfData;

listOfData.push_back(std::move(bigData));
// after this point, the contents of bigData are unspecified (however accessing it is _not_ undefined behavior)

没有移动语义,您需要将bigData复制并插入到listOfData中,或手动使用 swap() 将其移到新的空向量中,然后再添加到listOfData中。但是有了移动语义,由push_back调用的std::vector的右值引用构造函数(也称为移动构造函数)在复制新数据时会授权销毁bigData的旧内容-因此,它可以窃取bigData的内部指针并将其重置为空向量。

移动语义通常比COW语义更快,因为它们不需要维护共享只读数据的引用计数。但是,它们更受限制-您不能使用移动语义创建对数据的多个引用计数别名,只能在容器之间轻松方便地移动数据。

还要注意,最近版本的GCC和Microsoft Visual C ++支持右值引用和移动语义(GCC使用--std=c++0x),因此没有理由今天不开始使用它们。


1
定义垃圾:一个未知但有效的状态。 - Martin York
“bigData的内容未指定(但访问它不是未定义行为)” - 因此结果由被复制的类型决定,它的移动构造函数是什么?如果没有定义移动构造函数,std::move会退回到复制构造函数吗?或者退回到std::swap - Karl Knechtel
5
std::move并不会移动任何东西,它只是将对象转换为T&&右值引用类型,这可能会触发调用移动构造函数(或移动赋值运算符)。实际执行移动操作的是移动构造函数。就像默认的复制构造函数一样,默认的移动构造函数也会被创建。默认的移动构造函数会逐个元素地移动每个成员。对于基本类型,移动操作只是拷贝操作。对于用户定义类型,会调用移动构造函数。 - Nicol Bolas

1

复制-写入和移动构造函数是不同的东西。

让我们谈论一下广泛使用COW实现的字符串。假设您有一个支持COW和移动语义的字符串实现。然后考虑以下代码:

cow::string foo() { return string("foo"); } // move CTOR is in effect here.

cow::string a = foo(); // move ctor in effect
cow::string b = a; // copy ctor in effect

ab在这里将共享相同分配的字符序列。而在这里

std::string foo() { return string("foo"); } // move CTOR is in effect here.

std::string a = foo(); // move ctor in effect
std::string b = a; // copy ctor in effect

ab会分配并保存两个不同的字符序列,将内存消耗翻倍。


我了解到,虽然字符串的COW实现看似很明显,但实际上并不常见,因为人们发现它们会破坏多线程环境下的性能。 - Karl Knechtel
1
Karl,你有没有实际证明这个说法“它们会破坏多线程环境下的性能”?随着这些处理器等的出现,这听起来像是一个城市传说。是的,InterlockedIncrement和其他函数确实会产生一些开销,但在多线程环境中,额外的堆分配也需要一些同步原语。不清楚哪个更糟糕。无论如何,这非常依赖于具体情况。 - c-smile
1
我用“我的理解是”和“[其他]人发现”的短语来表达语句,有很好的原因。 ;) - Karl Knechtel
我们在COW、动态内存分配和锁方面有不好的经验。在多线程环境下,COW字符串并不总是好的选择。 - Viet

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接