移动构造函数与复制省略

问题描述

有人可以向我解释一件事吗?一方面,move constructor旨在通过消除不必要的复制对象来优化内存和处理器的使用,但是几乎在move constructor将要被编译器使用的所有地方copy elision ,禁用move ctor用法?是不是很不合情理?

解决方法

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

// 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开始)唯一有强制复制省略的实例是从函数调用或构造函数的结果构造值时。在这种情况下,甚至不允许编译器使用move构造函数。例如:

MyObject bar() {
    return MyObject();
}

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

通常,复制省略的目的不是完全消除移动构造,而是避免从prvalue初始化变量时避免不必要的构造。


请参见cppreference on Copy Elision

,

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

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构造函数旨在通过消除不必要的复制对象来优化内存和处理器的使用

那是不对的。移动构造将创建一个新对象,旧对象的数据被“移动” (在最坏的情况下,如果源对象的所有数据都完全封装在其中,则它的成本与常规副本)仅当您具有可以交换的成员变量(如支持交换的指针或容器)(或者如果它包含无法复制的资源)时,移动构造函数才比复制构造函数受益。

因此始终希望通过移动ctor进行复制省略。但这并不意味着移动ctor没有任何用处。但是,在许多情况下,移动ctor只是swap之上的语法糖,并且会被重置/清空/破坏(并非完全正确,而是紧密相关)。

除了swap情况外,移动ctor还可用于不可复制的内容以及您不希望使用指针的内容。例如std::uniqu_ptr由于拥有唯一的所有权而不能复制,但是您可能希望在调用函数时传递所有权,因此移动其资源很重要。

您可以将移动语义看作是一个标准化的过程,以便在可能的情况下复制省略号,并且在不影响移动ctor的情况下。