多重继承和纯虚函数

时间:2012-01-02 00:10:40

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

以下代码:

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'继承虚拟,修复问题,但我不明白为什么。有人可以解释一下发生了什么吗?

P.S。我故意省略了虚拟析构函数以缩短代码。请不要告诉我把它们放进去,我已经知道了。)

3 个答案:

答案 0 :(得分:14)

您的继承树中有两个 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()可以满足。{/ p>

有关详细信息,see this prior question - 答案提供了一些很好的图表,使这一点更加清晰。

答案 1 :(得分:11)

通常的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 - 公共 - > interface_base
  • 通过implementation_base私下继承:implementation - 私人 - > implementation_base - 公共 - > interface_base

当客户端代码执行其中一个派生到基本转换时:

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

重要的是,从派生类到给定的基类子对象,只有一个可访问的继承路径;其他不可访问的路径被简单地忽略。因为基类的继承只是虚拟的,所以只有一个基类主题,所以这些转换永远不会有歧义。

此处,implementationinterface_base的转换始终可以通过客户端代码interface完成;另一条无法进入的道路根本不重要。 唯一的interface_base虚拟基地是公开继承自implementation

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

答案 2 :(得分: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来实现,并且不需要是一个更合适的设计。