派生类函数的重实现规则(override-覆盖)
派生类函数的重实现 (override-覆盖 )规则
对于用过 C++的人大体都清楚:派生类可以重实现基类中声明为 virtual的函数,并且很清楚如果想实现正确的重写,必须满足:派生类重实现的函数的所有属性和基类 virtual函数一致,即函数签名, const限制均一样。同时为了更好地传达代码意图,重实现的 virtual函数最好添加冗余的 virtual关键字。
上面这些是重写的基本要求,对于重实现,还有 3个需要注意的地方:
1) 保证可替换性 : 任何派生类都必须遵守基类所承诺的前条件和后条件。当然改写后函数可以要求更少保证更多,反之不行。
2) 永远不要修改默认参数。切记:默认参数并非函数签名的组成部分 ,修改重实现函数的默认参数,并不会导致重实现失败,但会导致糟糕的默认参数错误。对于调用者而言,同一个对象的成员函数会不加提示地根据自己访问所使用的静态类型而接受不同的参数。换句话说,重实现函数的默认参数并不具有多态属性。
3) 谨防基类重载 (overload)函数被重实现函数所隐藏 (overwrite),。 (这和 C++的名字查找相关 )
示例:默认参数问题:
class Base {
// …
virtual void Foo( int x = 0 );
};
class Derived : public Base {
// …
virtual void Foo( int x = 1 ); // poor form, and surprise-inducing
};
Derived *pD = new Derived;
pD->Foo(); // invokes pD->Foo(1)
Base *pB = pD;
pB->Foo(); // invokes pB->Foo(0)
示例:隐藏基类重载函数:
class Base{// …
virtual void Foo( int );
virtual void Foo( int, int );
void Foo( int, int, int );
};
class Derived : public Base {// …
virtual void Foo( int ); // overrides Base::Foo(int), but hides the others
};
Derived d;
d.Foo( 1 ); // ok
d.Foo( 1, 2 ); // error (oops?)
d.Foo( 1, 2, 3 ); // error (oops?)
问题产生的原因: C++编译器的编译过程分 3步:名字查找,重载解析和访问性检查。为加快名字查找速度,编译器是逐步扩大名字查找范围的。以上述例子为例: d.Foo( 1, 2 ); 在名字查找时,编译器首先发现派生类存在 Foo函数,并停止名字查找;接着进行函数的重载解析,并发现派生类中,并未找到合适函数签名的 Foo函数,报错!
解决方案:使用 using 引入基类的特定名字 (见 < Using声明和指令的工作原理 >)。
class Derived : public Base {// …
virtual void Foo( int ); // overrides Base::Foo(int)
using Base::Foo; // bring the other Base::Foo overloads into scope
};