移动构造函数与复制省略

Move constructor vs Copy elision

谁能给我解释一件事。从一方面来看,move constructor 旨在通过消除不必要的对象复制来优化内存和处理器的使用,但从另一方面来看,几乎所有 move constructor 将被使用的地方,编译器使用 copy elision , 禁止使用 move ctor?是不是很不合理?

在很多情况下,移动构造函数仍会被调用,并且不会使用复制省略:

// inserting existing objects into a container
MyObject myobject;
std::vector<MyObject> myvector;
myvector.push_back(std::move(myobject));

// inserting temporary objects into a container
myvector.push_back(MyObject());

// swapping
MyObject other;
std::swap(myobject, other);

// calling functions with existing objects
void foo(MyObject x);

foo(std::move(myobject));

...还有更多。

唯一存在强制复制省略(C++17 起)的实例是从函数调用或构造函数的结果构造值时。在这种情况下,甚至不允许编译器使用移动构造函数。例如:

MyObject bar() {
    return MyObject();
}

void example() {
    MyObject x = bar(); // copy elision here
    MyObject y = MyObject(); // also here
}

一般来说,复制省略的目的不是完全消除移动构造,而是在从纯右值初始化变量时避免不必要的构造。


参见cppreference on Copy Elision

这是一个简单的示例,其中调用了 move。这是一个 玩具示例,零规则可能与之相关,但假设 class 中还有其他成员需要遵循五规则。

class A {
    std::string s;
public:
    A(const char* s = ""): s(s) {}
    ~A() {}
    A(const A& a): s(a.s) {
        std::cout << "copy ctor" << std::endl;
    }
    A& operator=(const A& a) {
        s = a.s;
        std::cout << "copy assignment" << std::endl;
        return *this;
    }
    A(A&& a): s(std::move(a.s)) {
        std::cout << "move ctor" << std::endl;
    }
    A& operator=(A&& a) {
        s = std::move(a.s);
        std::cout << "move assignment" << std::endl;
        return *this;
    }
};

int main() {
    A a;
    a = "hi"; // move
    // suppose we KNOW here that a is not needed anymore
    A a2 = std::move(a); // move
    a = "bye"; // move
}

代码:http://coliru.stacked-crooked.com/a/97d25c43e0edb00b

因为复制省略有限制,编译器必须知道对象的生命周期来预测是否可以进行复制省略。 例如:

std::vecter<MyObj> v;
v.push(MyObj()); // compiler has a higher chance to do the copy elision

但考虑一下:

MyObj my_obj;
v.push(my_obj)
// ...
// my_obj will never use

在这种情况下,编译器不会知道 my_obj 永远不会被使用,所以会执行正常的复制。如果效率很重要,您必须使用 v.push(std::move(my_obj)); 明确告诉编译器“我再也不会使用 my_obj”

move constructor was designed to optimize the memory & processor usage by eliminating unnecessary copying an object

事实并非如此。 move构造创建一个新对象,旧对象的数据"moved"(最坏情况下,如果源对象的所有数据都被完全包含在其中,就像作为常规副本昂贵)移动构造函数仅在您拥有可以像指针一样交换的成员变量或支持交换的容器(或者如果它包含无法复制的资源)时比复制构造函数受益

所以在移动构造函数上总是需要复制省略。但这并不意味着移动构造函数没有任何用处。然而,在许多情况下,move ctor 只是 swap 和 reset/empty/destruct 的语法糖(不完全正确,但接近)。

除了 swap 情况之外,移动构造函数对于可复制或不可复制以及您不想使用指针的事物也很有用。例如a std::uniqu_ptr 由于唯一的所有权而不应该是可复制的,但您可能希望在调用函数时传递所有权,因此移动其资源很重要。

您可以将 move sematic 视为一个标准化过程,以便在可能的情况下进行复制省略,如果它不是 move ctor 的回退。