简体   繁体   English

多重继承和纯虚函数

[英]Multiple inheritance and pure virtual functions

The following code: 以下代码:

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;
}

fails to compile with the following errors: 无法编译时出现以下错误:

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()

I have played around with it and figured out that making the 'interface -> interface_base' and 'implementation_base -> interface_base' inheritances virtual, fixes the problem, but I don't understand why. 我玩过它并想出让'interface - > interface_base'和'implementation_base - > interface_base'继承虚拟,修复问题,但我不明白为什么。 Can someone please explain what is going on? 有人可以解释一下发生了什么吗?

ps I omitted the virtual destructors on purpose to make the code shorter. ps我故意省略了虚拟析构函数以缩短代码。 Please don't tell me to put them in, I already know :) 请不要告诉我把它们放进去,我已经知道:)

You have two interface_base base classes in your inheritance tree. 继承树中有两个 interface_base基类。 This means you must provide two implementations of foo() . 这意味着您必须提供两个 foo() And calling either of them will be really awkward, requiring multiple casts to disambiguate. 调用它们中的任何一个都会非常尴尬,需要多个强制转换才能消除歧义。 This usually is not what you want. 这通常不是你想要的。

To resolve this, use virtual inheritance: 要解决此问题,请使用虚拟继承:

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;
}

With virtual inheritance, only one instance of the base class in question is created in the inheritance heirarchy for all virtual mentions. 对于虚拟继承,在继承层次结构中只为所有虚拟提及创建了一个有问题的基类实例。 Thus, there's only one foo() , which can be satisfied by implementation_base::foo() . 因此,只有一个foo() ,可以通过implementation_base::foo()来满足。

For more information, see this prior question - the answers provide some nice diagrams to make this all more clear. 有关更多信息, 请参阅此先前的问题 - 答案提供了一些很好的图表,使这一切更加清晰。

The usual C++ idiom is: 通常的C ++习语是:

  • public virtual inheritance for interface classes 接口类的公共虚拟继承
  • private non-virtual inheritance for implementation classes 实现类的私有非虚拟继承

In this case we would have: 在这种情况下,我们将:

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();
};

In implementation , the unique interface_base virtual base is : implementation ,唯一的interface_base虚拟基础是:

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

When client code does one of these derived to base conversions: 当客户端代码执行其中一个派生到基本转换时:

  • derived to base pointer conversions, 派生到基指针转换,
  • reference binding of base type with an initializer of static type derived, 基类型的引用绑定与派生的静态类型的初始化程序,
  • access to inherited base class members via a lvalue of derived static type, 通过派生静态类型的左值访问继承的基类成员,

what matters is only that there is a least one accessible inheritance path from the derived class to the given base class subobject; 重要的只是从派生类到给定的基类子对象至少有一个可访问的继承路径; other inaccessible paths are simply ignored. 其他不可访问的路径被简单地忽略。 Because inheritance of the base class is only virtual here, there is only one base class subject so these conversions are never ambiguous. 因为基类的继承只在这里是虚拟的,所以只有一个基类主题,因此这些转换永远不会模糊。

Here, the conversion from implementation to interface_base , can always be done by client code via interface ; 这里,从implementationinterface_base的转换总是可以通过interface通过客户端代码完成; the other inaccessible path does not matter at all. 另一条无法进入的道路根本不重要。 The unique interface_base virtual base is publicly inherited from implementation . 唯一的interface_base虚拟基础是从implementation公开继承的。

In many cases, the implementation classes ( implementation , implementation_base ) will be kept hidden from client code: only pointers or references to the interface classes ( interface , interface_base ) will be exposed. 在许多情况下,实现类( implementationimplementation_base )将保持对客户端代码的隐藏:仅暴露指针或接口类( interfaceinterface_base )的引用。

For the case of 'solving' the diamond inheritance problem, the solutions offered by bdonlan are valid. 对于“解决”钻石继承问题的情况,bdonlan提供的解决方案是有效的。 Having said that, you can avoid the diamond-problem with design. 话虽如此,你可以避免设计中的钻石问题。 Why must every instance of a given class be seen as both classes? 为什么必须将给定类的每个实例视为两个类? Are you ever going to pass this same object to a class that says something like: 你是否会将这个相同的对象传递给类似于以下内容的类:

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);
}

Surely it would be better in this case for Icecream to just implement NutritionalConsumable and provide a GetAsDrink() and GetAsFood() method that will return a proxy, purely for the sake of appearing as either food or drink. 当然,在这种情况下, Icecream只会实施NutritionalConsumable并提供GetAsDrink()GetAsFood()方法会GetAsFood()返回代理,这纯粹是出于食物或饮料的目的。 Otherwise that suggests that there is a method or object that accepts a Food but somehow wants to later see it as a Drink , which can only be achieved with a dynamic_cast , and needn't be the case with a more appropriate design. 否则,这表明有一种方法或对象接受Food但不知何故想稍后将其视为Drink ,这只能通过dynamic_cast来实现,而不需要采用更合适的设计。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM