2012-09-11 28 views
13

我有这样一段代码(从我的现实生活中的麻烦做作)使用子类型参数

它不能编译,抱怨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++允许改变返回类型的子类,而不是参数类型?

这是一个很好的基本原理还是语言规范中的一个错误点?

回答

14

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

C++标准允许您使用Covariant return type而overidding虚拟功能,但不允许修改功能parameters.And是它背后的好理由。

理由:

重写基本上意味着,无论是基类的方法或派生类的方法将在运行时间根据由所述指针所指向的实际对象上被调用。
这意味着:
例如:“可以调用Base类方法的每个实例都可以通过调用Derived类方法来替换,而不会更改调用代码。”

如果上述规则不适用,它会留下一个窗口,通过添加新功能(新派生类)来打破现有代码。

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

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

+2

对于该规则,参数不需要匹配100%。虽然*协变参数*会违反该规则,但是反变量参数不会破坏它(每个可以传递给基的参数也可以传递给派生类型),它们也是不允许的。部分原因是x方差不是免费的,必须由蹦床/ thunk来处理返回的参数(在协方差的情况下)。允许参数中的反差会大大(按指数规律?)增加所需的蹦床功能数量和虚拟表的大小。 –

8

错误非常明显:您需要void Run(A*)在您的课ExtendedB,但你没有。您所拥有的全部是void Run(ExtendedA*),但这不相同:基类承诺接受任意A - 指针,但您的ExtendedB::Run是挑剔的,只接受一个窄子集。

(你混淆协方差和逆变,但不相关的C++,因为C++不允许逆变覆盖。)

3

它只是两种不同的类型,它使两个不同的函数具有两个不同的签名。

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

+0

提及'override'的+1。 – xtofl

2

专门的返回类型使子类更严格 - 它将充当A。但是,将Run方法限制为仅接受原始参数的子类,使B不能充当A

继承模型是一种关系。你的代码违反了Liskov替代原则。

+0

2012年,C++不能再被认为是“纯粹的面向对象的指针”语言(直到1998年才有这种说法)。今天,C++继承只是一种可以服务于Liskov替换的聚合机制,但也可能有其他用途。 C++类不一定是OOP对象,因此继承不一定是“是 - 一个”关系:它依赖于上下文。更恰当的描述是“隐含地像一个”而不是“是一个”。 –