复制和移动构造函数之间的效率差异

时间:2016-12-09 07:04:18

标签: c++ c++11

C ++ 11引入了一个新的rvalue引用概念。我正在某处读它并发现以下内容:

class Base
{
public:
    Base()  //Default Ctor
    Base(int t)  //Parameterized Ctor

    Base(const Base& b)  //Copy Ctor
    Base(Base&& b)  //Move Ctor
};

void foo(Base b)     //Function 1
{}

void foo(Base& b)   //Function 2
{}

int main()
{
    Base b(10);
    foo(b);        -- Line 1 (i know of ambiquity but lets ignore for understanding purpose)
    foo(Base());   -- Line 2
    foo(2) ;       -- Line 3
}

现在我的理解有限,我的观察结果如下:

  1. 第1行只会调用复制构造函数,因为参数是左值。

  2. C ++ 11之前的第2行会调用复制构造函数和所有那些临时复制内容,但是定义了移动构造函数,这将在此处调用。

  3. 第3行将再次调用move构造函数,因为2将隐式转换为Base类型(rvalue)。

  4. 请纠正并解释上述任何观察结果是否错误。

    现在,这是我的问题:

    1. 我知道一旦我们移动一个物体,它的数据就会在呼叫位置丢失。所以,我在上面的例子中如何更改第2行以在foo中移动对象“b”(是使用std :: move(b)?)。

    2. 我读过移动构造函数比复制构造函数更有效。怎么样?我只能想到在移动构造函数的情况下我们在堆上有内存的情况不需要再分配。当我们在堆上没有任何内存时,这个陈述是否成立?

    3. 是否比通过引用传递更有效(不,对吧?)?

2 个答案:

答案 0 :(得分:8)

首先是你的“理解”:

正如我所看到的,它们原则上是正确的但你应该知道Copy elision这可能会阻止程序调用任何复制/移动构造函数。取决于你的编译器(-settings)。

关于你的问题:

  1. 是的,你必须调用foo(std :: move(b))来调用一个带有左值的右值的函数。 std :: move将进行演员表演。注意:std :: move本身不是move anything

  2. 使用move-constructor“可能”更有效率。实际上,它只允许程序员实现一些更高效的构造函数。示例考虑一个向量,它是一个类,指向一个保存数据的数组的指针(类似于std :: vector),如果你复制它就必须复制数据,如果移动它就可以传递指针并设置旧的一个到nullptr。 但正如我在Scott Effective Modern C++中读到的那样:不要认为你的程序会更快,因为你使用std :: move everywere。

  3. 这取决于输入的用法。如果你不需要函数中的副本,在大多数情况下,通过(const)引用传递对象会更有效。如果您需要副本,有几种方法可以执行此操作,例如copy and swap idiom但是作为

答案 1 :(得分:3)

  

C ++ 11之前的第2行会调用复制构造函数和所有那些临时复制内容,但是定义了移动构造函数,这将在此处调用。

正确,除了任何体面的优化器都会“删除”副本,这样在C ++ 11之前就可以避免副本,并且在C ++ 11之后可以避免移动。第3行也是如此。

  
      
  1. 我知道一旦我们移动一个物体,它的数据就会在呼叫位置丢失。
  2.   

取决于如何实现移动构造函数/赋值。如果您不知道,这是您必须承担的。

  

所以,我在上面的例子中如何更改第2行以在foo中移动对象“b”(是使用std :: move(b)?)。

完全。 std::move将表达式的类型更改为r值,因此调用了移动构造函数。

  

我读过移动构造函数比复制构造函数更有效。

在某些情况下可以。例如,std::vector的移动构造函数比复制快得多。

  

我只能想到在移动构造函数的情况下我们在堆上有内存的情况不需要再次分配。当我们在堆上没有任何内存时,这个陈述是否成立?

该语句并非普遍适用,因为对于具有普通复制构造函数的对象,移动构造函数不再有效。但拥有动态内存并非严格要求更有效的移动。更一般地,如果对象拥有任何外部资源(可以是动态内存),或者它可以是例如必须在析构函数中释放的引用计数器或文件描述符,则移动可能是有效的。因此,重新获得或重新计算副本 - 这可以在移动时避免。

  

它比通过引用传递更有效(不,对吧?)?

确实没有。但是,如果您打算在通过引用传递它的函数内移动对象,那么您将不得不传递非const引用,因此无法传递临时值。

简而言之:参考非常适合临时访问您保留的对象,移动非常适合放弃所有权。