【问题标题】:Use multiple inheritance to satisfy abstract base class使用多重继承满足抽象基类
【发布时间】:2014-08-26 03:12:40
【问题描述】:

为什么这不起作用?继承的函数签名是否微妙地不正确,或者抽象基类是否在成员函数被继承“之前”强制执行,或者是其他什么?如果没有函数包装器,这是否可以被说服?

#include <iostream>

struct AbsBase {
    virtual void foo() = 0;
    virtual void bar() = 0;
};

struct ProvideFoo {
    void foo() { std::cout << "foo\n"; }
};

struct ProvideBar {
    void bar() { std::cout << "bar\n"; }
};

struct Concrete : public ProvideFoo, public ProvideBar, public AbsBase {
    // I guess I could put function wrappers here... sigh...
    //void bar() {ProvideBar::bar();}
    //void foo() {ProvideFoo::foo();}
};

int main() {
    Concrete c;
    c.foo();
    c.bar();
}

【问题讨论】:

  • 继承不是这样工作的。
  • 什么是“那个”?更正式地说,为什么编译器告诉我 Concrete 有一个纯虚拟成员,而不是告诉我多个基类包含该成员,这有帮助吗?
  • @Praxeolitic 您问题的措辞与您在此处展示的示例完全无关!没有任何 abstract :-/ ...
  • 嗯? AbsBase 具有纯虚函数,因此是“抽象类”。
  • 我真的不明白你为什么这么讨厌这个问题。

标签: c++ abstract-class multiple-inheritance mixins


【解决方案1】:

为什么你的代码编译失败

我认为反对者对你有点苛刻,因为你通过单独的类提供两个纯虚函数的实现的推理具有一些直观的吸引力。

唉,你同时在做两件不相关的事情。 ProvideFooProvideBarAbsBase 抽象类完全无关。你也可以从AbsBase 继承它们,但是它们中的每一个仍然是一个抽象类。无论哪种情况,您当前的Concrete 都是一个抽象类,因为它至少派生自一个具有纯虚函数的类。您不能从此类类中创建对象。

修复代码,第一部分

最简单的方法是完全从AbsBase 中删除子类,并直接从ProvideFooProvideBar 中删除子类。当然,现在Concrete 内部没有virtual 函数,因此进一步的子类化不能轻易覆盖foobar 功能。

#include <iostream>

struct ProvideFoo {
    void foo() { std::cout << "foo\n"; }
};

struct ProvideBar {
    void bar() { std::cout << "bar\n"; }
};

struct Concrete : public ProvideFoo, public ProvideBar {};

int main() {
    Concrete c;
    c.foo();
    c.bar();
}

Live Example I

修复代码,第二部分

您还可以创建多个接口和多个具体实现,如下所示:

#include <iostream>

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

struct AbsBar {
    virtual void bar() = 0;
};

struct ProvideFoo: AbsFoo {
    void foo() { std::cout << "foo\n"; }
};

struct ProvideBar: AbsBar {
    void bar() { std::cout << "bar\n"; }
};

struct Concrete : public ProvideFoo, public ProvideBar {};

int main() {
    Concrete c;
    c.foo();
    c.bar();
}

Live Example II

修复代码,第三部分

现在再来一次:当使用virtual 关键字从AbsBase 继承ProvideFooProvideBar 时,您还可以使用virtual 继承

#include <iostream>

struct AbsBase {
    virtual void foo() = 0;
    virtual void bar() = 0;
};

struct ProvideFoo: virtual AbsBase {
    void foo() { std::cout << "foo\n"; }
};

struct ProvideBar: virtual AbsBase {
    void bar() { std::cout << "bar\n"; }
};

struct Concrete : public ProvideFoo, public ProvideBar {};

int main() {
    Concrete c;
    c.foo();
    c.bar();
}

这确实是高级 C++,如果您的类还包含成员数据,它会变得非常复杂。我更愿意为您的代码使用第二种解决方案。

Live Example III

【讨论】:

  • 但是 ProvideBar 和 ProvideFoo 不是从 AbsBase 继承的,它们在 Concrete 的上下文中是否以某种方式抽象?
  • 你也可以使用虚拟继承;)
  • @dyp 谢谢,正在更新,但这真的很深入,有点知识是一件危险的事情:)
【解决方案2】:

我没有在问题中说明这一点,但我真的很想知道为什么代码没有编译。 TemplateRex 对我提出的问题给出了很棒的答案。

也就是说,这里解释了为什么代码无法编译并且也没有抱怨成员名称不明确的原因。首先,这是可以编译的类似内容。

struct A {
    virtual void foo() { std::cout << "A::foo()\n"; };
};

struct B {
   void foo() { std::cout << "B::foo()\n"; }
};

struct C : public A, public B {};

int main() {
    // This is fine.
    C c;
    // Uncommenting the next line would cause an ambiguous member name lookup and
    // invalidate the program.
    //c.foo();
}

幸运的是,我们的程序不一定是格式错误的,因为可能会发生模棱两可的名称查找。如果确实发生了模棱两可的名称查找,我们的程序就是格式错误的。 10.2.7

通过添加更多 foo() 的定义,可以创建一个没有注释 c.foo() 的有效程序。

// Name lookup never proceeds to the base classes if it succeeds locally. 10.2.4
struct C : public A, public B {
    void foo() { std::cout << "C::foo()\n"; }
};

通过使 A::foo() 成为纯虚函数将 A 更改为抽象类可防止编译。

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

struct B {
   void foo() { std::cout << "C::foo()\n"; }
};

struct C : public A, public B {};

int main() {
    // This is illegal.
    C c;
    // The next line is irrelevant.
    //c.foo();
}

编译器错误表明结构 C 是抽象的。为什么?让我们从抽象类的确切含义开始。

"10.4 抽象类

2 抽象类是只能用作其他类的基类的类;抽象类的任何对象都不能被创建,除非是从它派生的类的子对象。如果一个类至少有一个纯虚函数,那么它就是抽象的。 "

显然C至少有一个纯虚函数,所以是一个抽象类,我们只能从它继承。在了解为什么 C 具有纯虚函数之前,我们已经可以回答部分问题。 C 是一个抽象类,我们试图创建一个实例,这是非法的。简单地创建对象是非法的。如果您从不尝试访问纯虚函数也没关系。

那么为什么 C 语言有一个纯虚函数呢?它必须是 A::foo()。 B::foo() 怎么了? A::foo() 是否以某种方式获得了优先权?

首先,我们通常说派生类“具有”它们继承的功能,但这掩盖了真正发生的事情。

" 10.1.4 [...] 对于最派生类的类格中非虚拟基类的每次不同出现,最派生对象 (1.8) 应包含该类型的相应不同基类子对象。 […] "

这里明确了派生类和基类保持不同。我们不只是继承一堆函数。现在,覆盖成员和访问多个同名成员之间的区别很明显。我们可以继承多个具有相同名称和签名的函数。如果我们注意范围,我们甚至可以引用单独的函数,但不明确的名称查找是非法的。

要不是抽象类,继承的纯虚函数必须被覆盖。我们确实继承了一个没有被覆盖的纯虚函数,所以我们有一个抽象类。顺便说一句,我们还继承了具有相同签名的非纯虚函数,但这只是无关紧要的琐事。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-11-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-03-25
    • 1970-01-01
    • 2018-12-07
    相关资源
    最近更新 更多