我有这段代码(由我的现实生活中的麻烦设计)
无法编译,抱怨ExtendsB没有实现B::Run(A* a)
。但是,理解A* Run();
class A { };
class ExtendsA : public A { };
class B
{
public:
virtual ~B(){}
virtual void Run(A* a) = 0;
virtual A* Run() = 0;
};
class ExtendsB : public B
{
public:
virtual ~ExtendsB(){}
// Not OK! It does not see it as an implementation of
// virtual void Run(A* a) = 0;
virtual void Run(ExtendsA* ea) {};
virtual ExtendsA* Run() { return new ExtendsA(); }; // OK
};
为什么C ++允许将返回类型更改为子类,而不是参数类型?
这是一个很好的理由还是仅仅是语言规范中的遗漏点?
答案 0 :(得分:15)
为什么C ++允许将返回类型更改为子类,而不是参数类型?
C ++标准允许您在覆盖虚拟函数时使用 Covariant return type ,但不允许您修改函数参数。是的,有一个好的背后的理由。
理由:
覆盖本质上意味着将在运行时调用Base类方法或Derived类方法,具体取决于指针指向的实际对象。
它意味着:
即:“可以通过调用Derived类方法替换调用Base类方法的每个实例,而无需更改调用代码。”
如果上述规则不存在,它将留下一个窗口,通过添加新功能(新派生类)来破坏现有代码。
如果在派生类中有一个函数原型,它与基类虚函数w.r.t参数不同,那么该函数不会覆盖基类函数,因为上面的规则被破坏了。
但是,Covariant返回类型不会破坏此规则,因为向上隐式发生并且Base类指针始终指向派生类对象而不进行任何转换,因此Standard会在返回类型上强制执行协变返回类型的这种情况。
答案 1 :(得分:8)
错误非常明确:您的班级void Run(A*)
需要ExtendedB
,但您没有。{1}}。你所拥有的只是void Run(ExtendedA*)
,但那不一样:基类承诺接受任何 A
- 指针,但你的ExtendedB::Run
更挑剔且只接受一个狭窄的子集。
(你很混淆协方差和逆变,但这与C ++无关,因为C ++不允许反变量覆盖。)
答案 2 :(得分:4)
它只是两种不同的类型,它使两个不同的功能具有两个不同的签名。
通常,如果您正在使用了解C ++ 11的编译器,则应在要覆盖其他函数的函数上使用override关键字。在您的情况下,由于抽象基类,错误变得明显,但在其他情况下,这样的错误会导致大量调试......
答案 3 :(得分:2)
专门使用返回类型会使子类更严格 - 它将充当A
。但是,限制Run
方法仅接受原始参数的子类会使B
不能充当A
。
继承模型是一种关系。您的代码违反了Liskov Substitution原则。