用 std::unique_ptr 管理组合对象最安全:它独占所有权、自动析构、禁止拷贝、允许移动,严格满足“整体销毁时部分必须一同销毁”且不可共享的组合语义。

用 std::unique_ptr 管理组合对象最安全
组合关系要求“整体销毁时,部分必须一同销毁”,且部分不能被外部共享或转移——这正好对应 std::unique_ptr 的语义:独占所有权、自动析构、禁止拷贝、允许移动。
直接裸指针(T*)或 std::shared_ptr 都不符合组合本质:T* 不自动清理,易泄漏;std::shared_ptr 允许外部持有,破坏“专属拥有”约束。
示例:
class Engine { /* ... */ };
class Car {
std::unique_ptr<engine> engine_;
public:
Car() : engine_(std::make_unique<engine>()) {}
// 析构函数无需写:engine_ 自动释放
};</engine></engine>
- 必须用
std::make_unique<engine>()</engine>初始化,避免裸new - 禁止写
engine_.reset(new Engine):绕过make_unique会丢失异常安全保证 - 若需延迟构造(如依赖参数),在构造函数体中调用
engine_ = std::make_unique<engine>(args...)</engine>
组合对象的生命周期必须严格绑定到宿主对象
组合不是“能访问就行”,而是“存在即依附”。一旦宿主对象析构,组合对象必须已销毁——这意味着不能把 std::unique_ptr 成员 move 出去,也不能让它指向栈对象或全局对象。
常见错误:
- 让
std::unique_ptr<engine></engine>指向局部变量:Engine e; engine_.reset(&e);→ 析构时 delete 栈地址,未定义行为 - 在成员函数中返回
*engine_或engine_.get()给外部长期持有 → 外部可能在宿主销毁后继续使用,悬垂指针 - 在拷贝构造/赋值中尝试复制
std::unique_ptr→ 编译失败,这是好事,说明你正在违反组合契约
正确做法:只提供非拥有访问,例如返回 const 引用:const Engine& get_engine() const { return *engine_; }
需要多态组合时,用基类 std::unique_ptr<base>
如果组合对象有多种实现(如 ElectricEngine / GasEngine),且通过统一接口使用,应声明为指向抽象基类的智能指针。
关键点:
- 基类析构函数必须是
virtual,否则std::unique_ptr<base>析构时不会调用派生类析构函数 - 仍用
std::make_unique<derived>()</derived>构造,类型擦除由指针完成 - 不要用
dynamic_cast回退到具体类型——组合关系下,使用者本就不该关心具体实现
示例:
class Engine { virtual ~Engine() = default; virtual void start() = 0; };
class ElectricEngine : public Engine { /* ... */ };
class Car {
std::unique_ptr<engine> engine_;
public:
Car(bool electric) : engine_(electric ?
std::make_unique<electricengine>() :
std::make_unique<gasengine>()) {}
};</gasengine></electricengine></engine>
避免在组合中混用原始指针和智能指针
一个类里既有 std::unique_ptr<a></a> 又有 B* 成员,会极大增加析构逻辑复杂度。原始指针无法表达所有权,读者无法判断它是否属于组合关系。
实际项目中最容易被忽略的是:组合对象内部又持有对宿主的原始指针(反向引用)。例如 Engine 里存一个 Car*。这本身不违法,但必须确保:
- 该指针仅用于回调或临时访问,绝不延长宿主生命周期
- 宿主析构前,必须显式清空该指针(如在
Car::~Car()中调用engine_->set_owner(nullptr)) - 绝不在
Engine的析构函数里通过该指针访问Car成员——此时Car可能已析构一半
更稳妥的做法是用 std::weak_ptr 替代原始指针,但这就不再是纯组合了,而是引入了观察者模式。真要这么做,得承认关系已变质。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











