【问题标题】:What is the correct way to implement Aggregation in modern C++? [closed]在现代 C++ 中实现聚合的正确方法是什么? [关闭]
【发布时间】:2016-08-31 09:45:48
【问题描述】:

上下文:

通常会出现我需要聚合的情况:一个对象使用另一个对象而不拥有它。也就是说,一些主模块会为其他人创建和共享一个对象。

但是,到目前为止,我还没有找到正确的实现方法。

以往的研究:

1) 类 C 指针: 共享对象作为指针提供。问题是要由程序员正确管理创建、共享和删除的顺序,很容易携带悬空指针。

int main()
{
    A a;
    B b(&a);
    return 0;
}

2) 弱指针: 使用共享/弱指针解决了悬空指针的问题。主要问题是:它避免使用堆栈对象。它还转发异常情况,在最好的情况下是一个有争议的话题,在最坏的情况下是不可能的(内核/低级代码)。

int main()
{
    std::shared_ptr<A> a(new A());
    std::weak_ptr<A> wa = a; // optional, but required to show the weak_ptr use
    B b(wa);
}

3) 共享指针: 仅使用共享指针与(2)有同样的问题,但另外 A 的所有权是未知的:这违反了几个设计准则。

4) 对象引用: 共享对对象的引用解决了悬空问题,并且可能是最简单的解决方案。另一方面,它强制聚合对象在构造函数中传递,它避免了赋值运算符,并且总体上限制了设计。

int main()
{
    A a;
    B( a );
}

5) 永远不要聚合,只是作为参数传递。 即使可能,这也会极大地增加某些函数的参数数量。在我看来,这太复杂了。

6) 单例模式 Singleton 允许从多个模块访问单个对象。但是,这只允许共享一个实例,并且违反了几条设计准则。

问题:

在现代 C++ 中实现聚合的正确方法是什么?

理想的目标是:

  • 使代码易于维护,避免错误。
  • 灵活地以不同方式使用它(例如堆栈/堆)
  • 没有太多额外的复杂性或晦涩的代码。

【问题讨论】:

  • 我认为这里没有足够的信息来回答这个问题。它取决于比您共享的更多的架构考虑因素。这些都是有效的选项,具体取决于其他内容,但在“现代”c++ 中,如果可能,您不希望使用原始指针(而且在大多数情况下也不需要使用 new
  • 没有一个真正的答案。选择最佳设计来解决您的特定问题。
  • 为什么B不拥有A?您是否有自定义垃圾收集器来回收堆? B 比 A 活吗?
  • 原始指针被模块传递和持有并没有错,这些模块通过它们的语义知道它们不拥有并且永远不会拥有它们传递的对象它们传递的对象的生命周期超过了它们自己的生命周期。在许多用例中,这是众所周知的,尤其是。关于建模聚合。如果不正确,则使用 shared_ptr 或weak_ptr,具体取决于您是否要延长聚合对象的生命周期。顺便说一句,w.r.t。 #5,共享参考解决悬空问题。
  • @davidbak 存在的问题是所有权从类型上不清楚。因此,non_owning_ptr&lt;T&gt;(世界上最愚蠢的智能指针)的提议使得所有权的缺乏变得清晰。

标签: c++ c++11 aggregation


【解决方案1】:

正如许多评论者所指出的,没有一种方法可以解决您的问题。但是,以 C++ 设计模式为生的人们已经对这类事情进行了详细讨论。对于关于更好地处理悬空指针(包括其中一位评论者的建议,template &lt;class T&gt; using non_owning_ptr&lt;T&gt; = T*)的精彩讨论,我建议 Bjarne Stroustrup 的 talk 关于 C++ 核心规则和指南,特别是从给出的链接开始的部分。 Herb Sutter 的后续talk 在这方面也很有用,介绍了一些关于静态分析工具的想法来强制执行这些规则。

我知道这不是您正在寻找的神奇解决方案,但就其价值而言,它似乎是人们方式更聪明且方式的解决方案比我想出的 C++ 更有经验。老实说,这就是 Bjarne Stroustrup 最近将大部分时间花在这些规则和指南上的原因:C++ 本身不存在对这类事情的完美解决方案,但有一些限制,我们可以更接近。

【讨论】:

    【解决方案2】:

    我需要聚合:一个对象使用另一个对象而不拥有它。

    这不是我熟悉的“聚合”的任何定义。但是为了这篇文章的目的,我会使用它。

    在 C++ 类型系统中,无法表达一个对象在不拥有该对象的情况下访问该对象的能力。实现这种关系的方法太多了,一个类型无法表达这个想法。

    假设我们有一些实例 A 可以访问实例 B 但不拥有 B。那么,您如何定义这两个实例之间的关系?如果 B 在没有事先通知 A 的情况下被破坏,这种关系是不安全的。那么,我们如何确保这一点?

    实际上有几十种方法。也许 B 保存在 shared_ptr 中,因此 A 可以将其引用保存在 weak_ptr 中,从而在 B 不再存在时得到通知。也许有一些特定的对象 C 同时拥有 A 和 B 并将确保这一点。也许代码的一般结构使得 B 不可能在 A 之前被销毁。也许 B 本身知道它何时链接到 A 并会在其析构函数中通知 A。或者代码可能很脆弱,但每个编写代码的人都非常注意确保 A 不会比 B 更长寿。

    没有单一的“实现它的正确方法”,因为“它”不是一回事。这是无数可能的关系。这些关系都不一定是错误的。它们中的大多数甚至不一定是坏代码。它们只是类型之间的隐含关系。

    Library Fundamentals v1 包含observer_ptr&lt;T&gt;,它只是一个指针包装器;它不传达所有权语义。但是,核心 C++ 指南建议 raw pointers be used for most of these use-without-ownership relations;而是建议使用 owner&lt;T&gt; 注释确实拥有某些东西的指针。

    【讨论】:

    • en.wikipedia.org/wiki/Object_composition#Aggregation 可能是我的单行描述太简单了。如果这不是您熟悉的定义,请您向我介绍一下您的聚合概念吗?关于这个问题,是的,有数百种聚合情况,这就是我要求“正确”方式的原因。同样,错误是一个非常复杂的主题,但我们都同意以几种方式来管理它们(异常、错误返回等)。我将明确地查看您的 observer_ptr/owner 模板。谢谢
    • @AdrianMaire:“有数百种聚合情况,这就是我要求“正确”方式的原因。”您似乎没有理解我的意思。因为有数百种这样的情况,所以没有“正确的方法”。不可能。
    猜你喜欢
    • 2019-07-26
    • 2023-04-09
    • 2020-02-27
    • 2011-11-06
    • 2018-08-12
    • 1970-01-01
    • 2015-09-10
    • 2014-04-07
    • 1970-01-01
    相关资源
    最近更新 更多