C++:关联、聚合和组合

Posted

技术标签:

【中文标题】C++:关联、聚合和组合【英文标题】:C++ : Association, Aggregation and Composition 【发布时间】:2014-11-04 22:15:17 【问题描述】:

我开始研究 OOAD,但我很难找到一个 C++ 代码示例来说明如何以编程方式实现 AssociationAggregationComposition。 (到处都有几篇文章,但它们与 C# 或 java 有关)。我确实找到了一两个例子,但它们都与我导师的指示相冲突,我很困惑。

我的理解是:

关联: Foo 有一个指向 Bar 对象的指针作为数据成员 聚合: Foo 有一个指向 Bar 对象的指针,并且 Bar 的数据被深度复制到该指针中。 组合: Foo 有一个 Bar 对象作为数据成员。

这就是我实现它的方式:

class Bar

    Baz baz;
;

//ASSOCIATION (with Bar)
class Foo

    Bar* bar;
    void setBar(Bar* _bar)
    
        bar = _bar;
    
;

//AGGREGATION (with Bar)
class Foo

    Bar* bar;
    void setBar(Bar* _bar)
    
        bar = new Bar;
        bar->baz = _bar->baz;
    
;


//COMPOSTION (with Bar)
class Foo

    Bar bar;
    Foo(Baz baz)
    
        bar.baz = baz;
    
;

这是正确的吗?如果没有,那么应该如何做呢?如果您还可以给我参考书中的代码,将不胜感激(以便我可以与我的导师讨论)

【问题讨论】:

programmers.stackexchange.com 更适合回答这个问题。 聚合和关联的区别is subtle和它们在c++中的实现方式一般是一样的。 我记得我在学校时对同样的术语感到非常困惑。然后,当我进入工作团队时,用于讨论对象关系的唯一术语是 is-a、has-a、继承和组合。也就是说,我知道 UML 有不同的聚合和组合符号,但我从未真正见过有人区分这两者。所以,在你的课程中找到答案,让你得到正确的答案,但不要过分强调。 【参考方案1】:

我将忽略聚合。它是not a very clearly defined concept,在我看来,它引起的混乱比它的价值还要多。作文和联想就够了,Craig Larman 这么告诉我的。这可能不是您的讲师正在寻找的答案,但无论如何它不太可能在 C++ 中与 Association 有任何不同。

没有一种方法可以实现组合和关联。您如何实现它们将取决于您的要求,例如关系的多样性。

作曲

实现组合的最简单方法是使用一个简单的Bar 成员变量,就像您建议的那样。我要做的唯一改变是在构造函数成员初始化列表中初始化bar

// COMPOSITION - with simple member variable
class Foo 
 private:
  Bar bar;
 public:   
  Foo(int baz) : bar(baz) 
;

使用构造函数初始化列表来初始化成员变量通常是一个好主意,它可以更快,并且在某些情况下,例如 const 成员变量,它是初始化它们的唯一方法。

使用指针实现组合也可能是有原因的。例如Bar 可能是多态类型,而您在编译时不知道具体类型。或者您可能想要转发声明Bar 以最小化编译依赖(参见PIMPL idiom)。或者这种关系的多重性可能是 1 到 0..1,并且您需要能够拥有一个空 Bar。当然,因为这是 Composition Foo 应该拥有 Bar 而在现代 C++11/C++14 世界中,我们更喜欢使用智能指针而不是拥有原始指针:

 // COMPOSITION - with unique_ptr
class Foo 
 private:
  std::unique_ptr<Bar> bar;
 public:   
  Foo(int baz) : bar(barFactory(baz)) 
;

我在这里使用了std::unique_ptr,因为FooBar 的唯一所有者,但如果其他对象需要std::weak_ptrBar,您可能希望使用std::shared_ptr

联想

关联通常会像您所做的那样使用指针来实现:

// Association - with non-owning raw pointer
class Foo 
 private:
  Bar* bar;
 public:   
  void setBar(Bar* b)  bar = b; 
;

当然,您需要确信Bar 会在Foo 使用它时仍然存在,否则您会有一个悬空指针。如果Bar 的生命周期不太清楚,那么std::weak_ptr 可能更合适:

// Association - with weak pointer
class Foo 
 private:
  std::weak_ptr<Bar> bar;
 public:   
  void setBar(std::weak_ptr<Bar> b)  bar = std::move(b); 
  void useBar() 
    auto b = bar.lock();
    if (b)
      std::cout << b->baz << "\n";
  
;

现在Foo 可以使用Bar 而不必担心留下悬空指针:

 Foo foo;
 
   auto bar = std::make_shared<Bar>(3);
   foo.setBar(bar);
   foo.useBar(); // ok
   
 foo.useBar(); // bar has gone but it is ok

Bar 的所有权确实不清楚的某些情况下,可以使用std::shared_ptr 实现关联,但我认为这应该是最后的手段。

关于使用Bar 指针的深层副本实现聚合。我不会说这是一个典型的实现,但正如我所说,这取决于您的要求。 您确实需要确保在Foo 析构函数中的bar 成员指针上调用delete,否则会出现内存泄漏(或使用智能指针)。

【讨论】:

以上是关于C++:关联、聚合和组合的主要内容,如果未能解决你的问题,请参考以下文章

OOP 中的 组合聚合和关联有什么区别?

何时使用关联、聚合、组合和继承?

关联,聚合和组合有什么区别?

关联、组合和聚合 - 使用 java 实现

UML类图关系(泛化、继承、实现、依赖、关联、聚合、组合)

依赖关联聚合和组合之间区别