【问题标题】:Efficiency difference between copy and move constructor复制和移动构造函数的效率差异
【发布时间】:2017-04-24 14:29:18
【问题描述】:

C++11 引入了右值引用的新概念。我在某处阅读它并发现以下内容:

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 行将再次调用移动构造函数,因为第 2 行将隐式转换为基本类型(右值)。

如果以上观察有误,请指正并说明。

现在,这是我的问题:

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

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

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

【问题讨论】:

  • 您的特定示例没有数据成员,因此 所有 这些选项可以什么都不做,并且仍然可以正常运行(“as-if”规则)

标签: c++ c++11


【解决方案1】:

首先谈谈你的“理解”:

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

关于你的问题:

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

  2. 使用移动构造函数“可能”更有效。事实上,它只使程序员能够实现一些更高效的构造函数。示例考虑一个向量,它是指向包含数据的数组的指针的类(类似于 std::vector),如果复制它,则必须复制数据,如果移动它,则只需传递指针并设置旧的nullptr。 但正如我在 Scott Meyers 的Effective Modern C++ 中所读到的那样:不要认为你的程序会因为你使用 std::moveeverywere 而变得更快。

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

【讨论】:

  • 我将详细说明第 3 点,即在使用参数时通过引用传递会导致取消引用开销,这可能导致性能比复制/移动更差,但这取决于。很好的第一个答案!
  • 我会写完这篇文章的!
【解决方案2】:

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

正确,除非任何体面的优化器都会“省略”副本,因此在 C++11 之前可以避免复制,而在 C++11 之后则可以避免移动。第 3 行也一样。


  1. 我知道一旦我们移动一个对象,它的数据就会在调用位置丢失。

取决于移动构造函数/赋值的实现方式。如果你不知道,这是你必须假设的。

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

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

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

在某些情况下可以。比如std::vector的move构造函数比copy快很多。

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

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

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

确实没有。但是,如果您打算在通过引用传递对象的函数中移动对象,则必须传递非常量引用,因此无法传递临时对象。

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

【讨论】:

  • 为什么会在 2) 中调用移动构造函数而不是复制构造函数?
  • @Nubcake 因为Base() 是prvalue。
  • 我仍然有点困惑,因为void foo(Base b) 将进行复制,而void foo(Base& b) 正在获取引用,那么移动构造函数在哪里调用Base
  • @Nubcake void foo(Base b) will do a copy 只有在传递左值参数时才会进行复制,否则就是移动。但在这种情况下,使用左值调用函数会使程序格式错误,因为重载决议会模棱两可(例如在示例第 1 行中)。
猜你喜欢
  • 1970-01-01
  • 2022-12-04
  • 2023-03-28
  • 2023-03-20
  • 1970-01-01
  • 2018-09-23
  • 2013-11-05
  • 2015-08-14
  • 1970-01-01
相关资源
最近更新 更多