C++中如何在对象中保存另一个对象实例(避免复制)及方案对比
问题解答
一、传递B实例与A中保存的最佳方式
你要实现的是让A和外部的B共享同一个实例,完全避免拷贝,所以必须用引用或指针,不能用值语义的对象存储。
1. 构造函数传参方式
构造函数直接传B的非const引用即可——传值会直接拷贝B,完全达不到共享的目的;传const引用的话,后续无法通过A修改B的状态,不符合你的需求。
2. A中保存的方式
优先选择保存B的引用,语法最简洁,操作起来和直接用原对象一样。但要注意核心约束:必须保证外部的B对象生命周期长于A,否则A里的引用会变成“悬空引用”,指向已销毁的对象,触发未定义行为。
修改后的代码示例:
class B; // 前向声明,让A提前知晓B的存在 class A { public: // 构造函数接收B的非const引用 A(B& b) : b_ref(b) {} // 返回B的引用,外部调用时直接操作原实例 B& GetB() { return b_ref; } private: // 保存B的引用,与外部的B是同一个实例 B& b_ref; }; class B { public: B() = default; void IncrementCounter() { counter++; } int GetCounter() { return counter; } private: int counter = 0; }; int main(){ B b; A a(b); // 将b的引用传给A b.IncrementCounter(); a.GetB().IncrementCounter(); // 此时两者的counter值均为2,完全一致 }
如果无法保证B的生命周期比A长,或者需要给A的实例做赋值操作(普通引用一旦绑定就无法更改指向),可以换成std::reference_wrapper<B>——它是对引用的封装,支持拷贝、赋值,还能存入容器,用法和引用几乎一致,只需把A的私有成员改成std::reference_wrapper<B> b_ref;,记得包含<functional>头文件。
二、指针/智能指针与引用/std::reference_wrapper的区别
1. 裸指针 vs 普通引用
- 空值特性:指针可以为空(比如
nullptr),引用必须始终指向有效的对象,不能为null。 - 指向修改:指针可以随时切换指向其他对象,引用一旦绑定就无法更换目标。
- 语法差异:指针需要用
*解引用、->访问成员;引用直接使用,语法和普通对象完全一致。 - 生命周期管理:两者都不管理对象的生命周期,需靠外部保证对象的有效性。
2. 智能指针 vs 引用/std::reference_wrapper
- 内存自动管理:智能指针(如
std::shared_ptr、std::unique_ptr)会自动管理对象生命周期——当最后一个智能指针销毁时,对象会被自动释放;引用和std::reference_wrapper不负责内存管理,对象的销毁由外部控制。 - 所有权属性:
std::shared_ptr支持共享所有权,多个指针可指向同一对象;std::unique_ptr是独占所有权;引用和std::reference_wrapper只是对象的“别名”,没有所有权概念。 - 使用场景:智能指针适合堆上分配的对象,需要自动释放内存时使用;引用和
std::reference_wrapper适合栈上或已由外部管理生命周期的对象,仅需共享访问时使用。
3. std::reference_wrapper vs 普通引用
- 拷贝赋值兼容性:普通引用不能作为容器元素(容器要求元素可拷贝赋值),
std::reference_wrapper可以,且赋值时能重新绑定到其他对象。 - 场景适配:在需要可拷贝的上下文(如模板参数、容器存储)中,
std::reference_wrapper能保持引用语义,普通引用会被退化为值拷贝。
内容的提问来源于stack exchange,提问作者heyloo
相关产品推荐
相关产品推荐

