15

我有这段代码(从我现实生活中的麻烦中设计出来的)

它无法编译,抱怨 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++ 允许将返回类型更改为子类,而不是参数类型?

这是一个很好的理由还是只是语言规范中的一个遗漏点?

4

4 回答 4

17

为什么 C++ 允许将返回类型更改为子类,而不是参数类型?

C++ 标准允许您在覆盖虚函数时使用协变返回类型,但不允许您修改函数参数。是的,它背后有一个很好的理由。

理由:

重写本质上意味着将在运行时调用基类方法或派生类方法,具体取决于指针指向的实际对象。
这意味着:
即:“可以调用基类方法的每个实例都可以通过调用派生类方法来替换,而无需对调用代码进行任何更改。”

如果没有上述规则,它将留下一个窗口,通过添加新功能(新的派生类)来破坏现有代码。

如果派生类中的函数原型与基类虚函数 wrt 参数不同,则该函数不会覆盖基类函数,因为上述规则被破坏。

但是,协变返回类型并没有违反此规则,因为向上转换是隐式发生的,并且基类指针始终可以指向派生类对象而无需任何转换,因此标准对返回类型强制执行协变返回类型的这一条件。

于 2012-09-11T12:02:22.450 回答
9

错误很明显:你需要void Run(A*)在你的类ExtendedB中,但你没有。你所拥有的只是void Run(ExtendedA*),但这不一样:基类承诺接受任何 A-pointer,但你的ExtendedB::Run更挑剔,只接受一个狭窄的子集。

(您混淆了协变和逆变,但这与 C++ 无关,因为 C++ 不允许逆变覆盖。)

于 2012-09-11T12:01:19.363 回答
4

它只是两种不同的类型,这使其成为具有两个不同签名的两个不同功能。

通常,如果您使用的是理解 C++11 的编译器,则应在旨在覆盖另一个函数的函数上使用 override 关键字。在您的情况下,由于抽象基类,错误变得明显,但在其他情况下,这样的错误可能会导致大量调试......

于 2012-09-11T12:02:26.243 回答
2

特殊化返回类型使子类更加严格——它将充当A. 但是,将Run方法限制为仅接受原始参数的子类使得B不充当A.

继承对is-a 关系建模。您的代码违反了 Liskov 替换原则。

于 2012-09-11T12:03:11.907 回答