18

以下代码:

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

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

4

3 回答 3

16

您的继承树中有两个 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()

有关更多信息,请参阅这个先前的问题- 答案提供了一些很好的图表,使这一切更加清晰。

于 2012-01-02T00:15:26.430 回答
12

通常的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虚基是:

  • 通过以下方式公开继承interfaceimplementation--public--> interface--public-->interface_base
  • 私有继承方式implementation_baseimplementation--private--> implementation_base--public-->interface_base

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

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

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

在这里,从implementation到 的转换interface_base总是可以由客户端代码通过interface; 其他无法访问的路径根本无关紧要。独特的interface_base虚拟基地是公开继承自implementation.

在许多情况下,实现类 ( implementation, implementation_base) 将对客户端代码隐藏:只有对接口类 ( interface, interface_base) 的指针或引用会被公开。

于 2012-08-01T20:39:26.530 回答
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()andGetAsFood()方法,纯粹是为了显示为食物或饮料。否则,这表明有一个方法或对象接受 aFood但以某种方式希望稍后将其视为 a Drink,这只能通过 a 来实现dynamic_cast,并且不需要更合适的设计。

于 2012-01-02T00:41:15.817 回答