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



我真的很困惑,我已经查了几次了,但它仍然没有点击。在内存使用方面,复制构造函数和移动构造函数在幕后是什么样子的?我真的不明白";窃取资源";与move构造函数一起使用。移动构造函数应该用于动态分配的内存还是堆栈上的内存?

还有人告诉我,如果我有这样的代码:

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

则CCD_ 1将被复制到CCD_。我的印象是,复制会在内存中创建一个新项目,并将传递的数据复制到该对象的内存地址。所以obj会在内存中创建一个新的空间,o的数据会被复制到其中。然而,我得到的oobj的地址完全相同,所以基本上我不知道发生了什么

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

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

我真的不明白什么"窃取资源";与move构造函数一起使用。

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

副本构造函数必须保持源对象不变,因此它必须为自己分配自己的对象数据副本。现在,这两个对象都引用了内存不同区域中相同数据的不同副本(为了开始讨论,我们不要像o0那样考虑引用计数的数据)。

另一方面,move构造函数可以简单地";移动";通过获得引用数据的指针的所有权,将数据本身留在其所在的位置,来处理数据。新对象现在指向原始数据,并且源对象被修改为不再指向数据。数据本身保持不变。

这使得移动语义

复制/值语义这里有一个例子来证明这一点:

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);
}
};
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

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

移动语义是

最常见的与指向动态资源的指针/句柄一起使用,是的(但在其他情况下移动语义也很有用)。更新指向数据的指针比复制数据更快。知道源对象将不再需要引用其数据,就不需要复制数据然后销毁原始数据,原始数据就可以"移动";正如从源对象到目的对象一样。

移动语义不能帮助提高效率的地方是;移动";是POD数据(纯旧数据,即整数、浮点小数、布尔值、结构/数组聚合等)"移动";这样的数据与";复制";例如,你不能;移动";一个int到另一个int,则只能复制其值。

还有人告诉我,如果我有这样的代码:。。。则CCD_ 13将被复制到CCD_。

someFunction(Obj obj)的示例中,是的,因为它通过值获取其obj参数,因此调用Obj副本构造函数从o创建obj实例。

不在obj0或someFunction(const Obj &obj)的示例中,不,因为它们通过引用来获取obj参数,因此根本没有创建新对象。引用只是现有对象的别名(在后台,它被实现为指向对象的指针)。将运算符的&地址应用于引用将返回被引用对象的地址。这就是为什么在这些示例中,main()someFunction()中打印的地址相同。

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

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

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

仅当objo副本时,是。

Remy的回答非常好,这里还有其他相关的问题。但是,如果答案看起来仍然有些";摘要";对你来说,最好的办法就是自己看看到底发生了什么。考虑下面的类。

void print_vec(auto v) {
std::cout << "[";
for(auto elem : v) {
std::cout << elem << ", ";
}
std::cout << "]" << std::endl;
}
class Myclass {
public:
// Public data to make inspection easy in this example
int a;
std::vector<int> v;
Myclass(int pa, std::vector<int> pv) : a(pa), v(std::move(pv)) {}
void print(std::string_view label) {
std::cout << label << " objectn    a stored in " << &a << " with value " << a << "n";
std::cout << "    v elements stored in " << v.data() << " with value ";
print_vec(v);
std::cout << std::endl;
}
};
int main(int argc, char *argv[])
{
Myclass obj1(10, {1,2,3,4,5});
std::cout << "xxxxxxxxxx Part 1 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" << std::endl;
obj1.print("obj1");
std::cout << "xxxxxxxxxx Part 2 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" << std::endl;
// Now let's create a copy -> This calls the copy constructor
auto obj2 = obj1;
obj1.print("obj1");
obj2.print("obj2");
std::cout << "xxxxxxxxxx Part 3 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" << std::endl;
// Now let's call the move constructor
auto obj3 = std::move(obj1);
obj1.print("obj1");
obj3.print("obj3");
return 0;
}

Myclass必须对成员进行数据处理。一个整数和一个CCD_ 31。如果你运行这个代码,你会得到下面的

xxxxxxxxxx Part 1 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
obj1 object
'a' stored in 0x7ffd1c8de0f0 with value 10
'v' elements stored in 0x55946fab8eb0 with value [1, 2, 3, 4, 5, ]
xxxxxxxxxx Part 2 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
obj1 object
'a' stored in 0x7ffd1c8de0f0 with value 10
'v' elements stored in 0x55946fab8eb0 with value [1, 2, 3, 4, 5, ]
obj2 object
'a' stored in 0x7ffd1c8de110 with value 10
'v' elements stored in 0x55946fab92e0 with value [1, 2, 3, 4, 5, ]
xxxxxxxxxx Part 3 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
obj1 object
'a' stored in 0x7ffd1c8de0f0 with value 10
'v' elements stored in 0 with value []
obj3 object
'a' stored in 0x7ffd1c8de130 with value 10
'v' elements stored in 0x55946fab8eb0 with value [1, 2, 3, 4, 5, ]

第1部分

这里我们只是打印原始对象。

第2部分

现在我们通过复制第一个对象来创建第二个对象。这将调用Myclass的复制构造函数。两个对象中的数据与预期的相同,但它们是副本,因为对象和``中的内存地址不同

第3部分

我们创建了另一个对象,但现在我们obj1移动到这个新对象。这调用了类的move构造函数。一旦一个对象从移动到,就像obj1一样,我们就不应该再使用它,除非我们给它分配一个新值。现在obj1.v中的内部指针是一个空指针,请注意obj3.v存储自己数据的地址指向了obj1.v之前指向的位置。这就是将数据从一个对象移动到另一个对象的含义。

但并不是所有的东西都可以或被移动。请注意,整数a成员被复制,而向量v成员被移动。虽然int的复制成本很低,但有时即使是复制成本不高的数据也无法移动。例如,如果我们将double arr[100]数据添加到Myclass,那么移动构造函数仍然会将obj1.arr复制到新的obj3对象,因为arr在堆栈中。

相关内容

最新更新