咨询C++类浅拷贝行为、拷贝初始化形式差异及指针解引用赋值原理
代码运行逻辑与相关疑问解答
参考代码
class A { public: int* x; }; class B { public: A _a; }; int main() { B b1; b1._a.x = new int(1); B b2(b1); *b2._a.x = 2; std::cout << *(b1._a.x) << std::endl; }
现有理解的校正与补全
你对最终输出结果的因果推导方向是正确的,但存在一处关键概念偏差,也遗漏了C++默认拷贝语义的核心原理:
- 错误认知:“b1与b2的
_a是同一个对象”。实际上b1和b2是两个完全独立的B类实例,二者内嵌的_a成员也是各自独立的A类对象,自始至终都不是同一个对象。 - 真正导致两个对象的指针指向同一块内存的核心原因:当类没有自定义拷贝构造函数时,C++会自动生成默认的逐成员拷贝构造函数,拷贝过程按值逐字节复制所有成员的内存内容:
- 拷贝B类对象时,会将b1的所有成员原样复制到b2的内存空间,其中就包括b1内嵌的A类
_a成员的全部内容 - 对A类的
int* x指针成员来说,逐字节复制的结果就是b2._a.x拿到了和b1._a.x完全相同的指针值——也就是两个独立A对象里的两个独立指针变量,恰好存储了同一个堆内存的地址
- 拷贝B类对象时,会将b1的所有成员原样复制到b2的内存空间,其中就包括b1内嵌的A类
- 后续修改这块公共堆内存的数值时,不管通过b1的x还是b2的x访问,读写的都是同一块内存空间,因此最终打印结果为2。这种逐字节拷贝带来的指针共享问题,就是常说的浅拷贝陷阱。
问题1:B b2(b1)与B b2 = b1是否始终等价?
- 同类型对象的普通拷贝场景下,两种写法的最终行为基本一致,
B b2 = b1本质上就是C++拷贝初始化的标准语法形式,语义上就是通过拷贝构造函数从b1生成b2。 - 明确存在不等价的场景:
- 当类的拷贝构造函数被
explicit修饰时:B b2(b1)是直接初始化写法,可以正常调用explicit拷贝构造函数;但B b2 = b1是拷贝初始化写法,会拒绝调用explicit修饰的构造函数,直接触发编译错误。 - 当赋值两侧类型不一致时:比如类定义了从其他类型到B的隐式转换构造函数,
B b2 = 其他类型实例会先生成临时B对象,再将临时对象拷贝给b2;而B b2(其他类型实例)会直接调用对应构造函数生成b2,不会产生临时对象。
- 当类的拷贝构造函数被
问题2:为什么*b2._a.x = 2可以直接对解引用结果赋值?是否和引用有关?
- 该操作的可行性完全由解引用运算符的固有语义决定,和引用的关联来自运算符本身的返回值设计:
- C++规定,对类型为
T*的指针执行*p解引用操作时,返回值类型是指向对象的左值引用T& - 左值的定义是拥有稳定内存地址、可以出现在赋值号左侧的表达式,本身就支持赋值操作
- 对
int*类型的b2._a.x做解引用时,返回的是它指向的堆上int变量的左值引用,自然可以直接作为赋值操作的左操作数,将2写入对应的内存地址。
- C++规定,对类型为
- 解引用返回引用类型是运算符的固有设计,不是赋值操作必须依赖引用才能完成。
内容的提问来源于stack exchange,提问作者Cristian Alam
相关产品推荐
相关产品推荐

