C++按位拷贝构造函数的内存共享问题及代码验证困惑
我完全懂你的困惑!刚啃拷贝构造函数这块的时候,很容易把按位拷贝(也就是浅拷贝)的作用场景搞混,咱们一步步拆解这个问题。
先看你引用的书里的核心内容:
Let's begin by restating the problem that the copy constructor is designed to solve. By default, when one object is used to initialize another, C++ performs a bitwise copy. That is, an identical copy of the initializing object is created in the target object. Although this is perfectly adequate for many cases—and generally exactly what you want to happen—there are situations in which a bitwise copy should not be used. One of the most common is when an object allocates memory when it is created. For example, assume a class called MyClass that allocates memory for each object when it is created, and an object A of that class. This means that A has already allocated its memory. Further, assume that A is used to initialize B, as shown here: MyClass B = A; If a bitwise copy is performed, then B will be an exact copy of A. This means that B will be using the same piece of allocated memory that A is using, instead of allocating its own. Clearly, this is not the desired outcome. For example, if MyClass includes a destructor that frees the memory, then the same piece of memory will be freed twice when A and B are destroyed!
这里的关键是**“对象创建时分配的内存”——特指堆上的动态分配内存**,而你写的代码里的id是另一回事!
你的代码为啥没出现书里说的共享问题?
你的Sample类里的id是一个直接成员变量,属于对象本身的一部分,当对象是栈上实例时,id也存在栈上。按位拷贝的时候,是把obj1.id的值(10)完完整整复制一份到obj2.id的独立内存空间里。这时候obj1和obj2是两个完全独立的对象,各自的id有自己的内存地址,修改obj1.id只会改它自己的那块内存,和obj2的id半毛钱关系都没有——这也是为啥你修改后obj2的id还是10的原因。
书里说的场景到底是啥样?
咱们把代码改成书里描述的包含动态内存的情况,你就能直观看到问题了:
#include <iostream> #include <cstring> using namespace std; class Sample { private: char* name; // 这个成员是指向堆内存的指针 public: int id; // 构造函数:动态分配内存存储名字 Sample(int x, const char* n) { id = x; name = new char[strlen(n) + 1]; // 在堆上分配内存 strcpy(name, n); } // 析构函数:释放堆内存 ~Sample() { cout << "释放name的内存:" << (void*)name << endl; delete[] name; } void display() { cout << "ID=" << id << ", Name=" << name << "(内存地址:" << (void*)name << ")" << endl; } // 修改名字内容 void changeName(const char* newName) { strcpy(name, newName); } }; int main() { Sample obj1(10, "Alice"); obj1.display(); // 隐式调用按位拷贝构造函数初始化obj2 Sample obj2(obj1); obj2.display(); cout << "\n修改obj1的名字为Bob后:" << endl; obj1.changeName("Bob"); obj1.display(); obj2.display(); // 你会发现obj2的名字也变成Bob了! return 0; // 程序结束时,obj1和obj2的析构函数都会释放同一块内存,触发double free错误 }
运行这段代码你会看到:
obj1和obj2的name指针指向完全相同的内存地址;- 修改
obj1的名字内容,obj2的名字也跟着变——因为它们共享同一块堆内存; - 程序退出时会报错(或出现未定义行为),因为两个对象的析构函数都试图释放同一块内存,也就是书里说的“同一块内存被释放两次”。
总结一下核心差异
- 对于值类型成员(比如
int、float,或者没有动态内存的对象):按位拷贝是安全的,复制的是值,两个对象的成员完全独立; - 对于包含指针指向堆内存的成员:按位拷贝只会复制指针的值(也就是内存地址),而不是指针指向的内容,这就导致两个对象共享同一块堆内存,这才是书里说的危险场景——这时候就需要自定义拷贝构造函数来实现深拷贝,为新对象单独分配堆内存,避免共享。
现在你应该明白书里的描述和你的代码为啥不一样了吧?
备注:内容来源于stack exchange,提问作者CREATIVITY Unleashed

