绝大多数情况下应直接存值;仅当需运行时多态、子对象生命周期独立、对象体积过大、构造可能失败时,才考虑std::unique_ptr或std::shared_ptr。

组合关系中该用 std::unique_ptr 还是直接存值,取决于对象是否需要多态、生命周期是否独立、以及拷贝成本是否可接受——绝大多数情况下,优先用值语义;只有明确需要运行时多态或避免栈溢出时,才引入指针。
什么时候必须用指针(std::unique_ptr 或 std::shared_ptr)
不是“想用组合就上指针”,而是这几个条件同时满足时才值得考虑:
- 基类有虚函数,且组合容器里要混存不同派生类型(比如
std::vector<:unique_ptr>></:unique_ptr>存AddNode、MultiplyNode) - 子对象生命周期必须脱离父对象独立管理(例如某个子节点可能被外部长期引用)
- 对象体积极大(如含大型数组、缓存或未初始化资源),直接值存储会导致栈溢出或拷贝开销不可控
- 构造逻辑复杂,可能失败(需延迟构造或异常安全保证),而父类构造函数又不能处理异常传播
注意:raw pointer 和 new 在现代 C++ 组合中基本没有正当理由——它绕过 RAII,破坏所有权语义,极易导致悬空或泄漏。
为什么多数情况该直接存值(MyChild m_child;)
值语义组合更简单、更快、更安全,尤其在以下场景:
- 子对象无虚函数、类型固定(如
std::string、std::vector<int></int>、自定义轻量Config类) - 父对象析构即意味着子对象终结(生命周期严格绑定)
- 拷贝/移动成本低(
sizeof(MyChild) ,且已实现移动语义) - 你想让整个组合对象支持
constexpr构造或 trivially copyable
示例:一个 Document 包含固定结构的 Header 和 Footer,直接值存储即可:
class Document {
Header m_header; // 值语义,自动构造/析构
Footer m_footer;
public:
Document() = default; // 无需手动管理
};
std::vector<t></t> 里存值还是存智能指针?
关键看 T 是否支持多态和是否允许拷贝:
- 如果
T是具体类型(如Button、Label),用std::vector<widget></widget>—— 容器自己管理内存,支持高效连续访问 - 如果
T是抽象基类(如UIElement),必须用std::vector<:unique_ptr>></:unique_ptr>,否则会切片(slicing) - 避免
std::vector<:shared_ptr>></:shared_ptr>除非真有多个所有者——它带来原子计数开销和缓存不友好
切片问题很隐蔽:std::vector<base> v; v.emplace_back(Derived{}); 会丢弃 Derived 特有部分,只剩 Base 子对象。
容易忽略的坑:组合 + 移动语义 + 指针混合使用
一旦用了指针,就必须显式处理移动构造/赋值,否则默认生成的版本会浅拷贝指针,引发双重释放:
- 若手动管理
raw pointer,必须禁用拷贝、实现移动、重写析构——几乎没人能一次写对 - 用
std::unique_ptr可自动解决移动语义,但要注意:移动后原对象变为nullptr,若组合逻辑依赖“非空”状态,需额外检查 - 值语义组合天然支持移动(编译器自动生成移动操作),且移动后源对象仍处于有效可析构状态
真正复杂的点不在“怎么写”,而在“谁拥有、谁销毁、谁能看到”。组合模式的健壮性,80% 取决于你是否从一开始就明确划清了所有权边界——而不是等出现 double free 或 use-after-free 时再补救。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











