多重继承和纯虚函数

发布于 2024-12-24 00:50:52 字数 890 浏览 6 评论 0原文

以下代码:

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

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

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. Can someone please explain what is going on?

p.s. I omitted the virtual destructors on purpose to make the code shorter. Please don't tell me to put them in, I already know :)

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

天冷不及心凉 2024-12-31 00:50:52

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

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

You have two interface_base base classes in your inheritance tree. This means you must provide two implementations of 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().

For more information, see this prior question - the answers provide some nice diagrams to make this all more clear.

诗化ㄋ丶相逢 2024-12-31 00:50:52

通常的 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 公开继承:实施 --公共--> 接口 --public--> interface_base
  • 通过implementation_base私有继承:implementation --private--> implementation_base --public--> interface_base

当客户端代码执行以下派生到基类转换之一时:

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

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

在这里,从implementationinterface_base的转换始终可以由客户端代码通过interface完成;另一条无法到达的路径根本不重要。 唯一的interface_base虚拟基是从implementation公开继承的。

在许多情况下,实现类(implementation,< code>implementation_base)将对客户端代码隐藏:仅公开接口类(interfaceinterface_base)的指针或引用。

The usual C++ idiom is:

  • 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 :

  • publicly inherited via interface: implementation --public--> interface --public--> interface_base
  • privately inherited via 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; the other inaccessible path does not matter at all. The unique interface_base virtual base is publicly inherited from 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.

祁梦 2024-12-31 00:50:52

对于“解决”钻石继承问题的情况,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 并提供一个 Icecream >GetAsDrink() 和 GetAsFood() 方法将返回代理,纯粹是为了显示为食物或饮料。否则,这表明有一个方法或对象接受 Food 但不知何故希望稍后将其视为 Drink,这只能通过 dynamic_cast 来实现,并且不必是更合适的设计。

For the case of 'solving' the diamond inheritance problem, the solutions offered by bdonlan are valid. 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. 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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文