C++中的复制构造函数和移动构造函数有什么区别

use*_*180 0 c++

我真的很困惑,我已经看了几次,它仍然没有点击。就内存使用而言,复制构造函数和移动构造函数在幕后是什么样子的?我真的不明白移动构造函数中的“窃取资源”是什么。移动构造函数应该用于动态分配的内存还是堆栈上的内存?

我还被告知,如果我有这样的代码:

void someFunction(Obj obj){
    //code
    cout << &obj << endl;
}

int main(){
    Obj o;
    cout << &o << endl;
    someFunction(o);
}
Run Code Online (Sandbox Code Playgroud)

o将被复制到obj. 我的印象是复制会在内存中创建一个新项目并将传递的数据复制到该对象的内存地址。因此obj会在内存中创建一个新空间,并将o的数据复制到其中。但是,我得到了与oand相同的确切地址obj,所以基本上我不知道发生了什么。

Rem*_*eau 7

就内存使用而言,复制构造函数和移动构造函数在幕后是什么样子的?

如果正在调用任何构造函数,则意味着正在内存中创建一个新对象。因此,复制构造函数和移动构造函数之间的唯一区别是传递给构造函数的源对象是否会将其成员字段复制移动到新对象中。

我真的不明白移动构造函数中的“窃取资源”是什么。

想象一个对象,它包含一个指向内存中其他地方的某些数据的成员指针。例如,astd::string指向动态分配的字符数据。或者std::vector指向一个动态分配的数组。或者std::unique_ptr指向另一个对象。

一个拷贝构造函数必须离开源对象完好,所以它必须分配它自己的对象的数据的副本本身。现在,两个对象都引用了不同内存区域中相同数据的不同副本(出于本次讨论的目的,我们不要考虑引用计数数据,例如 with std::shared_ptr)。

一个移动的构造,在另一方面,可以简单地“移动”,采取的是指数据的指针的所有权,让数据本身其所在的数据。新对象现在指向原始数据,源对象被修改为不再指向数据。数据本身保持不变。

这就是使移动语义复制/值语义更有效的原因。

下面是一个例子来证明这一点:

class MyIntArray
{
private:
    int *arr = nullptr;
    int size = 0;

public:
    MyIntArray() = default;

    MyIntArray(int size) {
        arr = new int[size];
        this->size = size;
        for(int i = 0; i < size; ++i) {
            arr[i] = i;
        }
    }

    // copy constructor
    MyIntArray(const MyIntArray &src) {
        // allocate a new copy of the array...
        arr = new int[src.size];
        size = src.size;
        for(int i = 0; i < src.size; ++i) {
            arr[i] = src.arr[i];
        }
    }

    // move constructor
    MyIntArray(MyIntArray &&src) {
        // just swap the array pointers...
        src.swap(*this);
    }

    ~MyIntArray() {
        delete[] arr;
    }

    // copy assignment operator
    MyIntArray& operator=(const MyIntArray &rhs) {
        if (&rhs != this) {
            MyIntArray temp(rhs); // copies the array
            temp.swap(*this);
        }
        return *this;
    }

    // move assignment operator
    MyIntArray& operator=(MyIntArray &&rhs) {
        MyIntArray temp(std::move(rhs)); // moves the array
        temp.swap(*this);
        return *this;
    }

    /*
    or, the above 2 operators can be implemented as 1 operator, like below.
    This allows the caller to decide whether to construct the rhs parameter
    using its copy constructor or move constructor...

    MyIntArray& operator=(MyIntArray rhs) {
        rhs.swap(*this);
        return *this;
    }
    */

    void swap(MyIntArray &other) {
        // swap the array pointers...
        std::swap(arr, other.arr);
        std::swap(size, other.size);
    }
};
Run Code Online (Sandbox Code Playgroud)
void copyArray(const MyIntArray &src)
{
    MyIntArray arr(src); // copies the array
    // use arr as needed...
}

void moveArray(MyIntArray &&src)
{
    MyIntArray arr(std::move(src)); // moved the array
    // use arr as needed...
}

MyIntArray arr1(5);                // creates a new array
MyIntArray arr2(arr1);             // copies the array
MyIntArray arr3(std::move(arr2));  // moves the array
MyIntArray arr4;                   // default construction
arr4 = arr3;                       // copies the array
arr4 = std::move(arr3);            // moves the array
arr4 = MyIntArray(1);              // creates a new array and moves it

copyArray(arr4);                   // copies the array
moveArray(std::move(arr4));        // moves the array

copyArray(MyIntArray(10));         // creates a new array and copies it
moveArray(MyIntArray(10));         // creates a new array and moves it
Run Code Online (Sandbox Code Playgroud)

移动构造函数应该用于动态分配的内存还是堆栈上的内存?

移动语义最常与指向动态资源的指针/句柄一起使用,是的(但在其他场景中移动语义可能有用)。更新数据指针比复制数据更快。知道源对象将不再需要引用其数据,因此无需复制数据然后销毁原始数据,原始数据可以按原样从源对象“移动”到目标对象。

当被“移动”的数据是 POD 数据(纯旧数据,即整数、浮点小数、布尔值、结构/数组聚合等)时,移动语义无助于提高效率。“移动”这样的数据与“复制”它是一样的。例如,你不能“移动”一个int到另一个int,你只能复制它的值。

我还被告知,如果我有这样的代码:......那o将被复制到obj.

在 的示例中someFunction(Obj obj),是的,因为它按值获取其obj参数,从而调用 的复制构造函数以从 中创建实例。Objobjo

不在someFunction(Obj &&obj)or的例子中someFunction(const Obj &obj),不,因为它们通过引用来获取obj参数,因此根本没有创建新对象。引用只是现有对象的别名(在幕后,它被实现为指向该对象的指针)。将address-of 运算符应用于引用将返回被引用的对象的地址。这就是为什么你看到被打印在同一个地址,并在这些例子。&main()someFunction()

我的印象是复制会在内存中创建一个新项目并将传递的数据复制到该对象的内存地址。

基本上,是的。更准确地说,它将传递对象的成员字段的复制到新对象的相应成员字段中。

因此obj会在内存中创建一个新空间,并将o的数据复制到其中。

仅当obj是 的副本o,是的。