【问题标题】:Multiple inheritance and pure virtual functions多重继承和纯虚函数
【发布时间】:2012-01-31 13:27:00
【问题描述】:

以下代码:

struct interface_base
{
    virtual void foo() = 0;
};

struct interface : public interface_base
{
    virtual void bar() = 0;
};

struct implementation_base : public interface_base
{
    void foo();
};

struct implementation : public implementation_base, public interface
{   
    void bar();
};

int main()
{
    implementation x;
}

编译失败,出现以下错误:

test.cpp: In function 'int main()':
test.cpp:23:20: error: cannot declare variable 'x' to be of abstract type 'implementation'
test.cpp:16:8: note:   because the following virtual functions are pure within 'implementation':
test.cpp:3:18: note:    virtual void interface_base::foo()

我玩过它并发现将“接口 - > interface_base”和“implementation_base - > interface_base”继承虚拟化可以解决问题,但我不明白为什么。有人可以解释发生了什么吗?

附言我故意省略了虚拟析构函数以使代码更短。请不要告诉我把它们放进去,我已经知道了:)

【问题讨论】:

标签: c++ virtual multiple-inheritance diamond-problem


【解决方案1】:

您的继承树中有两个 interface_base 基类。这意味着您必须提供foo()两个 实现。调用它们中的任何一个都会非常尴尬,需要多次转换才能消除歧义。这通常不是您想要的。

要解决此问题,请使用虚拟继承:

struct interface_base
{
    virtual void foo() = 0;
};

struct interface : virtual public interface_base
{
    virtual void bar() = 0;
};

struct implementation_base : virtual public interface_base
{
    void foo();
};

struct implementation : public implementation_base, virtual public interface
{   
    void bar();
};

int main()
{
    implementation x;
}

使用虚拟继承,只有一个相关基类的实例会在所有虚拟提及的继承层次结构中创建。因此,只有一个foo(),可以满足implementation_base::foo()

如需更多信息,see this prior question - 答案提供了一些漂亮的图表,让这一切更加清晰。

【讨论】:

  • 对于foo() 的两个实现,我有没有办法说“使用implementation_base 的实现”,而不使用虚拟继承?
  • @HighCommander4,您可以从implementation_base 派生接口,反之亦然,从而消除菱形并创建线性继承树。除此之外,不,但如果你想隐藏它,你可以将 interface_baseinterface 设为空包装器,它们实际上是从 real 接口类派生的。
【解决方案2】:

对于“解决”菱形继承问题,bdonlan 提供的解决方案是有效的。话虽如此,您可以避免设计的钻石问题。为什么必须将给定类的每个实例都视为两个类?您是否曾经将同一个对象传递给类似以下内容的类:

void ConsumeFood(Food *food);
void ConsumeDrink(Drink *drink);

class NutritionalConsumable {
  float calories() = 0;
  float GetNutritionalValue(NUTRITION_ID nutrition) = 0;
};
class Drink : public NutritionalConsumable {
  void Sip() = 0;
};
class Food : public NutritionalConsumable {
  void Chew() = 0;
};
class Icecream : public Drink, virtual public Food {};

void ConsumeNutrition(NutritionalConsumable *consumable) {
  ConsumeFood(dynamic_cast<Food*>(food));
  ConsumeDrink(dynamic_cast<Drink*>(drink));
}

// Or moreso
void ConsumeIcecream(Icecream *icecream) {
  ConsumeDrink(icecream);
  ConsumeFood(icecream);
}

当然,在这种情况下,Icecream 最好只实现 NutritionalConsumable 并提供将返回代理的 GetAsDrink()GetAsFood() 方法,纯粹是为了显示为食物或饮料。否则,这表明有一个方法或对象接受Food,但不知何故希望稍后将其视为Drink,这只能通过dynamic_cast 来实现,并且不需要更多适当的设计。

【讨论】:

  • 你的例子和我的很不一样。在我的例子中,implementation 派生自 interface,因为它将多态地用作 interface*,并且它派生自 implementation_base 以重用 interfaceinterface_base 部分的实现。
  • 好的,那么谁需要将其视为implementation_base?谁与interface 通信而不是interface_baseimplementation_base 继承自 interface_base 而不是 interface 背后的想法是什么?虽然我的问题很抽象,但你是在解决真正的系统设计还是只是钻石问题?
  • 没有人需要将其视为implementation_base,我可以从interface 派生它并在implementation_base 中复制实现——但我不想重复。
  • 也许我应该把它写成“见C++ composition vs private inheritance”。这篇文章/教程不仅讨论了在何时何地使用它,它还引用了 Scott Meyers 的基本声明,以及向您展示如何通过编写 using interface_base::foo; 使 foo() 公开可用。
【解决方案3】:

通常的 C++ 习语是:

  • 接口类的公共虚拟继承
  • 实现类的私有非虚拟继承

在这种情况下,我们会:

struct interface_base
{
    virtual void foo() = 0;
};

struct interface : virtual public interface_base
{
    virtual void bar() = 0;
};

struct implementation_base : virtual public interface_base
{
    void foo();
};

struct implementation : private implementation_base,
                        virtual public interface
{   
    void bar();
};

implementation 中,唯一的interface_base 虚拟基是:

  • 通过interface公开继承:implementation --public--> interface --public--> interface_base
  • 通过implementation_base私有继承:implementation --private--> implementation_base --public--> interface_base

当客户端代码执行其中一种派生到基础的转换时:

  • 派生到基指针转换,
  • 基类型的引用绑定与派生的静态类型的初始值设定项,
  • 通过派生静态类型的左值访问继承的基类成员,

重要的是从派生类到给定的基类子对象至少有一个可访问的继承路径;其他无法访问的路径将被忽略。因为基类的继承在这里只是虚的,所以只有一个基类主体,所以这些转换永远不会有歧义。

在这里,从implementationinterface_base 的转换,总是可以由客户端代码通过interface 完成;其他无法访问的路径根本无关紧要。 唯一的interface_base 虚拟基础是从implementation 公开继承的。

在许多情况下,实现类(implementationimplementation_base)将对客户端代码隐藏:只有对接口类(interfaceinterface_base)的指针或引用会被暴露。

【讨论】:

  • 我不会说“接口类的公共虚拟继承”是“(或)通常的 C++ 习语”。也许应该是,但据我所知肯定不是。
  • @PaulGroke Hum.. 你可能是对的。正确的成语?推荐的成语?
  • 我写了“也许应该是”,因为老实说我不确定它是否应该。我几乎从不使用虚拟继承(事实上我不确定我是否曾经在生产代码中使用过它)。所以我不知道它会导致什么问题,哪些问题也会影响纯接口类的虚拟继承(即平凡的ctor、平凡的dtor、无数据)。
  • 我也对这个“接口类的公共虚拟继承”感兴趣。我以前在任何地方都没听说过。尽管它在大多数情况下听起来都适用(对于接口)。你能回忆起任何来源吗?我无法搜索任何合适的内容。
  • @Mikhail 大多数是 Stroustrup 的原著(我应该说圣经吗?):TC++PL、D&E(几十年前读过,不太确定)。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2012-08-06
  • 1970-01-01
  • 1970-01-01
  • 2014-06-02
  • 2010-10-02
  • 2015-08-07
  • 2018-10-09
相关资源
最近更新 更多