为什么这不起作用?继承的函数签名是否巧妙地不正确,或者是强制执行的抽象基类"之前"成员函数是继承的还是其他的东西?这可以说服没有函数包装器吗?
#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();
}
答案 0 :(得分:7)
我认为downvoters对你有点苛刻,因为你通过单独的类提供两个纯虚函数的实现的理由具有一些直观的吸引力。
唉,你正在同时做两件无关的事情。ProvideFoo
和ProvideBar
与AbsBase
抽象类完全无关。您也可以从AbsBase
继承它们,但是它们中的每一个仍然是一个抽象类。在任何一种情况下,您当前的Concrete
都是一个抽象类,因为它派生自至少一个具有纯虚函数的类。你不能从这些类创建对象。
最简单的方法是完全从AbsBase
中删除子类,并直接从ProvideFoo
和ProvideBar
中删除子类。当然,现在您在virtual
内没有Concrete
个功能,因此进一步的子类化不能轻易覆盖foo
和bar
功能。
#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();
}
您还可以创建多个接口和多个具体实现,如下所示:
#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();
}
现在对于安可:使用virtual
关键字
ProvideFoo
继承ProvideBar
和AbsBase
时,您也可以使用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 ++,如果你的类也包含成员数据,它会变得非常复杂。我更愿意为您的代码使用第二种解决方案。
答案 1 :(得分:0)
我没有在问题中明确这一点,但我确实想知道为什么代码没有编译。在我问这个问题时,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抽象类
2抽象类是一个只能用作其他类的基类的类;除了作为派生自它的类的子对象之外,不能创建抽象类的任何对象。如果一个类至少有一个纯虚函数,则它是抽象的。 “
显然C至少有一个纯虚函数,因此是一个抽象类,我们只能从它继承。在了解为什么C具有纯虚函数之前,我们已经可以回答部分问题。 C是一个抽象类,我们试图创建一个非法的实例。只需创建该对象是非法的。如果您从未尝试访问纯虚函数,则无关紧要。
那么为什么C具有纯虚函数?它必须是A :: foo()。 B :: foo()发生了什么事? A :: foo()以某种方式优先考虑吗?
首先,我们通常说派生类“拥有”它们继承的功能,但这会模糊实际发生的事情。
” 10.1.4 [...]对于最派生类的类网格中非虚拟基类的每个不同出现,最派生对象(1.8)应包含该类型的相应不同基类子对象。 [...] “
这里明确指出派生类和基类保持不同。我们不仅仅继承了一堆功能。现在,重写成员和访问多个具有相同名称的成员之间的区别很明显。我们可以继承具有相同名称和签名的多个函数。如果我们小心范围但是模糊的名称查找是非法的,我们甚至可以参考单独的函数。
要不是抽象类,必须重写继承的纯虚函数。我们确实继承了一个未被覆盖的纯虚函数,因此我们有一个抽象类。顺便说一句,我们还继承了一个具有相同签名的非纯虚函数,但这只是无关紧要的琐事。