C++ 中虚函数override
动作,从而使得基类接口调用派生类函数成为了可能,而这个override
动作工作起来,需要满足如下要求:
-
基类中函数必须是虚函数。
-
基类和派生类中的函数名字必须完全相同(析构函数除外)。
-
基类和派生类中的函数形参型别必须完全相同。
-
基类和派生类中的函数常量性必须完全相同。
-
基类和派生类中的函数返回值和异常规格必须兼容。
-
基类和派生类中的函数引用饰词必须完全相同。(C++11以后,含11要求)
函数引用饰词
这个语言特性以下做讲解:
class Widget {
public:
void doWork() &; //这个版本的doWork仅在*this是左值时候调用
void doWork() &&; //这个版本的doWork仅在*this是右值时候调用
};
Widget makewidget(); //工厂函数(返回右值)
Widget w; //普通对象(左值)
...
w.doWork(); //以左值版本调用
makewidget().doWork(); //以右值版本调用
那么既然改写有这么多要求,就意味着一个小错误就会造成大的偏差。示例代码如下:
class Base {
public:
virtual void mf1() const;
virtual void mf2(int x);
virtual void mf3() &;
void mf4() const;
};
class Derived : public Base {
public:
virtual void mf1();
virtual void mf2(unsigned int x);
virtual void mf3() &&;
void mf4() const;
}
那么区别在于:
- Base 中的mf1是声明为const的,但是在Derived中的没有。
- Base 中的mf2的形参型别是int,而Derived中的则是unsigned int。
- Base 中的mf3带有左值引用饰词,而Derived中的带有右值引用饰词。
- Base 中的mf4未声明为虚函数。
实际上上述情况一个都无法完成改写,更有甚者,编译器完全不会报告任何错误信息,这一点是极为可怕的,容易埋下祸根。而C++11开始提供了一种显示标记的方式让编译器检查改写是否成功。代码如下:
class Derived : public Base {
public:
virtual void mf1() override;
virtual void mf2(unsigned int x) override;
virtual void mf3() && override;
void mf4() const override;
}
一旦这么写后,编译器会强制检查,所标记函数必须能够改写基类对应接口,否则则会进行报错。
而override
的好处不仅仅在于强制让编译器检查改写,还可以在打算更改基类虚函数签名的时候,能够通过编译器的报错衡量波及范围。
需要强调的是,虽然override
是个关键字,但是仅仅在特定语境下保留。对于已经将override
作为一个变量或者一个函数名的时候,依旧不受影响。
题外话,引用饰词
这里写个对于引用饰词的使用场景:
class Widget {
public:
using DataType = std::vector<double>;
...
DataType& data() & // 对于左值Widget型别,返回左值
{return values;}
DataType data() && // 对于右值Widget型别,返回右值
{return std::move(values);}
}
// 对于以下调用能够高效进行
Widget w;
...
auto vals1 = w.data();
auto vals2 = makeWidget().data();
如果没有引用饰词,那么对于右值版本会将临时对象中的std::vector进行复制到vals2中。而显然对于vector这种型别来说,移动操作一般比复制操作开销要小。
要点速记 |
---|
1. 为意在改写的函数添加override声明。 |
2. 成员函数引用饰词使得对于左值和右值对象可以处理可以区分开。 |