使用多重继承来满足抽象基类

Use multiple inheritance to satisfy abstract base class

本文关键字:抽象 基类 满足 多重继承      更新时间:2023-10-16

为什么这不起作用?继承的函数签名是否微妙地不正确,或者是抽象基类强制"之前"的成员函数被继承或它是别的什么?在没有函数包装器的情况下,这能被说服工作吗?

#include <iostream>
struct AbsBase {
    virtual void foo() = 0;
    virtual void bar() = 0;
};
struct ProvideFoo {
    void foo() { std::cout << "foon"; }
};
struct ProvideBar {
    void bar() { std::cout << "barn"; }
};
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();
}

为什么你的代码编译失败

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

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

修改代码,第一部分

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

#include <iostream>
struct ProvideFoo {
    void foo() { std::cout << "foon"; }
};
struct ProvideBar {
    void bar() { std::cout << "barn"; }
};
struct Concrete : public ProvideFoo, public ProvideBar {};
int main() {
    Concrete c;
    c.foo();
    c.bar();
}

实例1

修复你的代码,第二部分

你也可以创建多个接口和多个具体的实现,像这样:

#include <iostream>
struct AbsFoo {
    virtual void foo() = 0;
};
struct AbsBar {
    virtual void bar() = 0;
};
struct ProvideFoo: AbsFoo {
    void foo() { std::cout << "foon"; }
};
struct ProvideBar: AbsBar {
    void bar() { std::cout << "barn"; }
};
struct Concrete : public ProvideFoo, public ProvideBar {};
int main() {
    Concrete c;
    c.foo();
    c.bar();
}
示例二

修复你的代码,第三部分

现在为encore:您也可以使用virtual继承时,ProvideFooProvideBar的子类从AbsBase使用virtual关键字

#include <iostream>
struct AbsBase {
    virtual void foo() = 0;
    virtual void bar() = 0;
};
struct ProvideFoo: virtual AbsBase {
    void foo() { std::cout << "foon"; }
};
struct ProvideBar: virtual AbsBase {
    void bar() { std::cout << "barn"; }
};
struct Concrete : public ProvideFoo, public ProvideBar {};
int main() {
    Concrete c;
    c.foo();
    c.bar();
}

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

实例三

我在问题中没有说清楚,但我确实想知道为什么代码不能编译。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();
}

编译器错误表明struct C是抽象的。为什么?让我们从抽象类到底意味着什么开始。

10.4抽象类 抽象类是只能作为其他类的基类使用的类;抽象类的对象只能作为派生类的子对象创建。如果一个类至少有一个纯虚函数,那么它就是抽象的。"

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

那么为什么C语言有纯虚函数呢?必须是A::foo()。B::foo()发生了什么?A::foo()以某种方式优先吗?

首先,我们通常说派生类"有"它们继承的函数,但这模糊了真正发生的事情。

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

这里很清楚派生类和基类是不同的。我们不仅仅继承了一堆函数。现在,重写成员与访问多个同名成员之间的区别已经很明显了。可以继承具有相同名称和签名的多个函数。如果注意作用域,甚至可以引用单独的函数,但不明确的名称查找是非法的。

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