让我在序言中说,我已经阅读了许多关于移动语义的问题中的一些。这个问题不是关于如何使用移动语义,而是询问它的目的是什么--如果我没有弄错,我不明白为什么需要移动语义。
背景
我正在实现一个沉重的类,为了这个问题,这个类看起来如下所示:
class B;
class A
{
private:
std::array<B, 1000> b;
public:
// ...
}到了做移动赋值操作符的时候,我意识到我可以通过将b成员更改为std::array<B, 1000> *b;来显着地优化这个过程--然后移动可能只是一个删除和指针交换。
这就引出了以下的想法:现在,不应该所有非原始类型的成员都是加速移动的指针(在1下面修正),(在不应该动态分配内存的情况下需要做一个例子,但是在这些情况下,优化移动不是一个问题,因为没有办法这样做)?
下面是我的以下实现--为什么要创建一个类A,它实际上只包含一个指针b,所以当我只需创建指向整个A类本身的指针时,稍后的交换就更容易了。显然,如果客户机期望移动速度比复制快得多,那么动态内存分配应该是可以的。但是在这种情况下,为什么客户机不只是动态地分配整个A类呢?
问题
客户端不能利用指针来完成移动语义提供的所有事情吗?如果是,那么移动语义的目的是什么?
移动语义:
std::string f()
{
std::string s("some long string");
return s;
}
int main()
{
// super-fast pointer swap!
std::string a = f();
return 0;
}指针:
std::string *f()
{
std::string *s = new std::string("some long string");
return s;
}
int main()
{
// still super-fast pointer swap!
std::string *a = f();
delete a;
return 0;
}这是每个人都说的伟大的任务:
template<typename T>
T& strong_assign(T *&t1, T *&t2)
{
delete t1;
// super-fast pointer swap!
t1 = t2;
t2 = nullptr;
return *t1;
}
#define rvalue_strong_assign(a, b) (auto ___##b = b, strong_assign(a, &___##b))好吧--在这两个例子中,后者都可能被认为是“糟糕的风格”--不管这意味着什么--但这真的值得用双符号来解决所有的麻烦吗?如果在调用delete a之前抛出异常,这仍然不是一个真正的问题--只需设置一个保护程序或使用unique_ptr即可。
编辑1 --我刚刚意识到,对于使用动态内存分配本身并具有高效迁移方法的类(如std::vector )来说,这是不必要的。这使我的一个想法无效--下面的问题仍然存在。
编辑2,正如在下面的评论和答案中提到的,几乎没有什么意义。我们应该尽可能多地使用值语义,以避免分配开销,因为如果需要的话,客户机总是可以将整个事件转移到堆中。
发布于 2013-11-30 22:25:22
我非常喜欢所有的答案和评论!我同意他们所有人的观点。我只想再坚持一个还没人提过的动机。这来自于N1377
Move语义主要是关于性能优化:将昂贵的对象从内存中的一个地址移动到另一个地址,同时窃取源的资源,以便以最小的开销构造目标。 移动语义在某种程度上已经存在于当前语言和库中:
所有这些操作都涉及将资源从一个对象(位置)转移到另一个对象(至少在概念上)。缺少的是统一的语法和语义来支持泛型代码移动任意对象(就像今天的泛型代码可以复制任意对象一样)。标准库中有几个地方将极大地受益于移动对象而不是复制对象的能力(将在下面深入讨论)。
也就是说,在泛型代码(如vector::erase )中,需要一个单一的统一语法来移动值,以堵塞已删除的值留下的漏洞。人们不能使用swap,因为当value_type是int时,这样做太昂贵了。而且不能使用复制分配,因为当value_type是A (OP的A)时,这样做太昂贵了。嗯,一个人可以使用副本分配,毕竟我们在C++98/03做了,但它是荒谬的昂贵。
不应该所有非原始类型成员都是加速移动的指针吗?
当成员类型为complex<double>时,这将是非常昂贵的。不如给它涂上Java的颜色。
发布于 2013-11-30 21:57:57
客户端不能利用指针来完成移动语义提供的所有事情吗?如果是,那么移动语义的目的是什么?
您的第二个示例给出了一个非常好的理由,说明为什么移动语义是一件好事:
std::string *f()
{
std::string *s = new std::string("some long string");
return s;
}
int main()
{
// still super-fast pointer swap!
std::string *a = f();
delete a;
return 0;
}在这里,客户端必须检查实现,以确定谁负责删除指针。使用move语义,这个所有权问题甚至不会出现。
如果在调用
delete a之前抛出一个异常,这仍然不是一个真正的问题,只需设置一个保护程序或使用unique_ptr即可。
同样,如果不使用迁移语义,就会出现丑陋的所有权问题。顺便问一下,如果没有移动语义,您将如何实现unique_ptr?
我知道auto_ptr,现在有很好的理由反对它。
这真的值得所有的麻烦与双符号吗?
是的,这需要一些时间来适应它。在你熟悉并适应了它之后,你会想知道没有移动语义你怎么能活下去。
发布于 2013-11-30 21:55:53
您的字符串示例非常好。短字符串优化意味着空闲存储中不存在短std::string,相反,它们存在于自动存储中。
new/delete版本意味着强制每个std::string进入免费商店。move版本只将大字符串放入免费存储中,小字符串保持(并可能被复制)在自动存储中。
此外,指针版本缺乏异常安全,因为它有非RAII资源句柄。即使不使用异常,裸指针资源所有者基本上也会强制单个出口点控制流来管理清理。最重要的是,使用裸指针所有权会导致资源泄漏和指针悬空。
因此,裸露的指针版本在很多方面都更糟糕。
move语义意味着您可以将复杂对象视为正常值。当您不希望重复状态时,您会move,而copy则不希望重复状态。几乎正常的不能复制的类型只能公开move (unique_ptr),其他类型可以为它优化(shared_ptr)。存储在容器(如std::vector )中的数据现在可以包含异常类型,因为它是move感知的。std::vector of std::vector从可笑的低效和难以使用到在标准版本的冲击下变得简单和快速。
指针将资源管理开销放入客户端,而好的C++11类则为您处理这个问题。move语义使得这既易于维护,又不容易出错。
https://stackoverflow.com/questions/20305607
复制相似问题