【问题标题】:Creating an object from instances of the classes it (multiply) inherits from从它(乘)继承的类的实例创建一个对象
【发布时间】:2018-10-06 03:09:21
【问题描述】:

假设我有一个继承自 I、S1 和 S2 的 C 类。假设 I 只是一个接口,而 S1 和 S2 是没有函数的简单结构。没有一个类/结构共享任何公共成员或方法,也不继承任何其他东西。

保证不会出现钻石等级等问题。结构的目的只是提供组合的替代方案。

代码如下所示:

class C : public I, public S1, public S2
{
  ...
};

现在,这样做的有用之处在于,只要我需要一个只是其中一部分的对象,我就可以简单地将 C 的实例转换为 S1 或 S2。关键是使用现有的 C 作为 S1 或 S2(甚至是 I)很简单(而且很有用)。

但是,在该用例之上,我还希望能够在提供 S1 对象和 S2 对象时创建 C 的新实例。第一个(也是唯一一个)想到的是一个构造函数,它可以像这样工作:

C::C(const S1& s1, const S2&s2) : I(), S1(s1), S2(s2)
{
  ...
}

但是,该解决方案存在三个问题:

  • 它需要我的附加代码和附加构造函数 C 类非常不雅,我认为必须有一个 更好的方法。
  • 它要求我的结构 S1 和 S2 提供复制构造函数(或移动构造函数,如果实现方式不同)。虽然这对我来说很好,即使使用默认构造函数,这清楚地表明这不是一个通用的解决方案,因此很糟糕或至少不完整。
  • 由于框架的设计以及我在其中制作的应用程序,此解决方案将需要比仅在准系统示例中更多的功能和代码。再一次,编写代码并不是什么大问题,只是需要维护和思考更多的代码。最重要的是,在 C 中使用带有 S1 和 S2 实例的简单组合最终会变得相当冗长,甚至更少。

所以,总结一下:

当我们已经完成了这些类的对象(或者只是制作它们所必需的东西,甚至)时,有哪些可能的方法来创建一个从某些类继承的对象?

在那些最普遍、最简单和最不可能导致头痛和并发症的方式中,它们是逻辑上的还是句法上的?

【问题讨论】:

  • 如果你想继续使用组合并且仍然希望你的类型可以隐式转换为它的组件类型,你可以实现隐式user defined conversions
  • 确实如此,但这不是我决定放弃作曲的原因。这是在我的类中拥有 S1 和 S2 实例的语法开销。 S1 和 S2 在设计中是非常原始的构建块(并且在多个地方都需要),我很快就变得非常恼火,因为我必须不断编写一些东西.x 只是为了得到我在基本上 80% 的成员函数中使用的 x .此外,“x”也用于许多循环条件,我不喜欢在其中包含这样(相对)复杂和长的表达式。我更喜欢我的循环只是
  • 您能描述一下您想象的在理想世界中创建C 的样子吗?我不清楚你在找什么。
  • 您可以为C 提供赋值运算符,一个以S1 和一个以S2 作为参数来支持它。
  • @user8145466 static_cast<S1&>(C) = someS1; 会这样做。如果没有引用,您将从 C 创建一个临时的 S1 并尝试分配给它。

标签: c++ constructor multiple-inheritance


【解决方案1】:

理想情况下,如果我可以有一些未完全构建的C 然后只做c = s1; c = s2; 并且只有cs1s2 部分会随着每个分配而改变,那就太好了。

你知道什么!您可以很容易地实现这种精确的语法。事实是,每个基类都有一个您继承的operator =。它只是被派生类自己的operator = 隐藏(无论是否隐式生成)。因此,您只需要将它们全部带回范围:

class C : public I, public S1, public S2
{
public:
    using I::operator=;
    using S1::operator=;
    using S2::operator=;
};

... 然后其行为与您描述的完全一样。但是,基类是默认构造的,这可能会延伸您所说的“未完全构建”的含义。不幸的是(?)不可能有一个有效的派生对象,其基类根本没有被初始化。

See it live on Coliru

【讨论】:

  • "事实是,每个基类都有一个operator =",如果它被隐式删除的话。如果没有复制构造函数,可能就是这种情况。如果有operator=,那么按照 0 规则没有复制 c'tor 是不合理的。如果想要为分阶段初始化提供一种方法,那么导入这些运算符确实会有所帮助,这似乎是真正的重点。但是该对象并不是真正不完全构建的。这些部分是默认的或以其他方式初始化的。
  • @luk32 一个被删除的函数仍然是一个函数,它参与重载决议等等。
  • 我的意思是 OP 表示他们在提供副本 c'tor 方面存在问题。这应该转换为operator=。他们要么确实提供了它,即使他们不知道它,或者他们很可能需要为C 自定义显式实现这些运算符。就这样。 Taht 说,我认为您的回答仍然非常有用,因为 OP 似乎没有意识到这样一个事实,即每个复合类型都可能为 operator= 单独重载。所以想要的语法是很有可能的。实施可能会有所不同。
  • 我没有说我在提供复制 ctor 时遇到了问题。我刚刚说了你所说的同样的话,虽然在这种情况下它对我有用,但这不是一个通用的解决方案,我想要通用的解决方案。我还说过,由于我使用的框架,在我的情况下制作构造函数也很复杂。基本上,它通过设计强制一个 RA 然后初始化模型,这意味着对于每个构造函数,我还需要编写一个额外的静态创建函数。无论如何,这不是什么大问题,但对于这个简单的事情,这已经是两个额外的功能了。
  • 但是 Quentin 的解决方案(类似于 Francois 的手动创建 operator=s)也可以很好地满足我的需求,我只是不确定这是否能达到我的预期。这对我来说似乎很直观,但由于某种原因它也感觉有点不对,我希望这里的人们会指出如果我这样做会发生的各种内存泄漏和对象切片。令人惊喜的是它的定义很好。
【解决方案2】:

通过继承进行组合在支持它的语言中是一件事情,但它被认为是低于标准的做法,甚至是 OOP 中的反模式与组合。

通过继承,您可以获得自动的方法实现和转换。但是你放弃了对 API 的控制。并且您在S1S2 之间引入耦合,因为它们的实现相互限制,它们不能在不引入消歧的情况下使它们的成员命名相同。恕我直言,由于这种方式更加隐含,因此存在更多陷阱。

通过组合,您可以保留灵活性和细粒度控制,但代价是显式间接......以及随之而来的实现工作。如果需要,基本上这两个 API 都必须复制,在您的情况下似乎是这样。

在其他方面它们几乎相同,您可以编写在客户端方面具有相同可用性的代码。例如。转换和分配。

无论哪种方式,您都需要或不需要复制 c'tors。对于继承,您可以提供 make_C 设施和转发 c'tor 参数。 S1S2 必须提供可以重用的任何构造方式,这两种方法都是如此。您应该能够为继承模式编写适当的 c'tors。

如果您需要将 C 与其成分分离,您还需要通过成员身份以某种方式复制组合。

您不能使用继承使C 成为围绕引用的外观,就像您可以通过使用对组件的引用来进行成员资格组合一样。但是,如果您需要在C 的实例及其各自的部分之间共享身份,那么这不是真正的组合关系。

要说一般来说哪个会不那么令人头疼几乎是不可能的,这是一个设计决定。更通用和灵活的是成员关系组合,更简单的可能是继承。但我不确定我是否会放弃它。

我会说设计越封闭和僵化,您可以肯定没有干扰继承对其专业人士使用更安全。否则,它可能是 PIA 并且需要在某个时间点进行重构。如果你有大量的类,重复接口将是 PIA。

【讨论】:

  • 我很清楚这些问题,这就是为什么我明确提到 S1 和 S2 是两个完全不相关的 POD 结构。事实上,他们的数据原本是 C 本身的,但出于各种原因,我决定将其解耦。因此,它们本质上与 C 相关。它们分开的唯一原因是在语法上促进和简化从磁盘写入和读取 C。所以关键是,在这种特殊情况下,继承基本上没有缺点。纯组合给我带来的唯一好处是每次我需要 C 中的一些数据时都必须访问一个代理对象。
  • 我知道你知道,并且有约束加强多重继承的情况。在一般情况下,我试图列出一些利弊。我相信我已经揭穿了一两个假设。设计决定权在你。如果您必须复制粘贴整个 API,并且您确定对象已关闭,您似乎能够做出明智的决定。我想说,只要您只需要一个子组件就复制数据是不利的。否则你将不得不提取一个接口并使用它。
  • 嗯,组合的实现已经存在并且工作正常。我只是对它如何使我的代码变得混乱感到恼火,并决定用 MI 版本过期,看看我有多喜欢它。但我被困在这部分。无论如何,这是一个个人计划,我没有任何截止日期或任何东西,所以虽然我很欣赏你的理论见解,但我宁愿自己做,看看我喜欢什么它与我以前的相比。如果它爆炸,它就会爆炸。
  • 但是,我绝对理解一般赞成/反对答案的价值。特别是因为我在任何地方都找不到类似的问题(尽管它可能存在)。也许在您的答案中添加一些通用代码示例将有助于实现该目标。
  • 它是如何弄乱你的代码的?除了必须显式编写间接之外,它应该是相似的,使用 getter 和 setter 方法而不是成员。客户端方面应该是相似的。 “S1 和 S2 是两个完全不相关的”和“它们分开的唯一原因是为了在语法上促进和简化 Cs 的书写和阅读”似乎是自相矛盾的。如果它们完全不相关,那么这就是将它们分开的唯一原因。顺便说一句,您是否考虑过扭转这种方法并拥有C + 两个轻量级外观接口以满足您的需求?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-07-01
  • 2011-12-08
  • 2021-12-27
  • 1970-01-01
相关资源
最近更新 更多