在C++编程中,常常使用两种方式来组合对象:聚合和组合。这些概念反映了类之间的不同关系,并影响相关对象的生命周期和销毁责任。
问题的背景:
在面向对象设计中,始终重要的是区分对象之间的依赖关系。随着面向对象语言(如Smalltalk、C++、Java)的出现,如何更好地建模“部分—整体”关系的问题变得更加突出。在C++中,由于需要手动管理内存和对象生命周期,这一问题变得尤为重要。
问题:
在聚合和组合之间错误选择,可能导致内存泄漏、资源重复或对象销毁错误。此外,这些概念经常被混淆。
解决方案:
代码示例:
// 组合: class Engine {}; class Car { Engine engine; // Engine随Car的创建和销毁而创建和销毁 }; // 聚合: class Person {}; class Team { std::vector<Person*> members; // 指向Person对象,但不拥有它们 };
关键特性:
如果在类成员中有一个指向对象的指针,这总是聚合吗?
不一定!如果类拥有此指针(例如,通过std::unique_ptr),这仍然是组合。关系类型不是由字段类型决定,而是由生命周期的责任决定。
class House { std::unique_ptr<Room> room; // 组合,House拥有Room };
组合可以通过引用或原始指针实现吗?
可以 — 但仅在对象由拥有者创建和销毁,且引用或指针用于优化的情况下。然而,更好的做法是使用值对象或智能指针以明确表示所有权。
如果在组合中部分对象在拥有者之外创建并传递给它,会发生什么?
在这种情况下,这可能导致组合的不变性被破坏:如果外部创建的对象被传递给拥有者并被销毁,而某处仍有指向它的引用 — 将会出现悬空指针。必须严格定义项目中的所有权和销毁责任。
一个团队决定通过原始指针在容器中存储所有嵌套对象,并在析构函数中手动销毁它们。一切正常,直到他们更改了所有权模型。结果指针被释放了两次,导致崩溃。
优点:
缺点:
另一个团队改为对所有真正的所有权关系使用std::unique_ptr,而非拥有的仅在作为临时引用时使用。这明确表达了架构。
优点:
缺点: