重写的概念
一个类A中声明的虚函数fun在其派生类中再次被定义,且B中的函数根A中fun的原型一模一样(函数名,参数列表,返回值等一样),那么我们称B重写(override)了A的fun函数。对于任何B类型的变量,调用成员函数fun都是调用B重写的版本。如果同时A的派生类C,没有重写A的fun函数,C类型的变量在调用成员函数fun则会调用A中的版本。这在C++中就实现多态。
final用法
基类A中的成员函数fun被声明为virtual的,那么对于其派生类B而言,fun总是能够被重载的(除非被重写了)。又时候我们并不想fun在B类型派生类中被重载,C++98无法对此限制。
#include <iostream>
using namespace std;
class MathObject
{
public:
virtual double Arith() = 0;
virtual void Print() = 0;
};
class Printable : public MathObject
{
public:
double Arith() = 0;
void Print() //在C++98中不能阻止该接口被重写
{
cout << "output is:" << Arith() << endl;
}
void Print(int a)
{
cout << a << "output is:" << Arith() << endl;
}
};
class Add : public Printable
{
public:
Add(double a, double b) : x(a), y(b) {}
double Arith()
{
return x + y;
}
private:
double x, y;
};
class Mul : public Printable
{
public:
Mul(double a, double b, double c) : x(a), y(b), z(c) {}
double Arith()
{
return x * y * z;
}
private:
double x, y, z;
};
在上述代码中,基类MathObject定义了两个接口:Arith和Print。类Printable继承MathObject并实现了Print接口。接下来,Add和Mul继承了Printable,可以使用MathObject的接口和Printable的Print的实现。倘若Printable和Add有两个不同的程序员编写,如果Add的编写者重载了Print函数,那么统一的风格的打印方式将不复存在。
final的关键字的作用是使派生类不可覆盖它所修饰的虚函数。
struct Object{
virtual void fun() =0;
};
struct Base:public Object{
void fun() final; //声明为final
};
struct Derived : public Base{
void fun(); //编译报错
};
再上述代码中,派生于Object的Base类从写了Object的fun接口,并本类的fun函数声明为final的。那么再派生于Base的Derived类对接口的重写则会导致编译错误。
基类的虚函数也可以使用final进行修饰。被final修饰的函数无法被重写,则失去了虚函数的意义。如果不想成员函数被重写,可直接将改函数定义为非虚函数。final通常只在继承关系的“中途”终止派生类重写中有意义。
override用法
C++重写对于基类声明为virtual的函数,在派生类中的重写函数则不需要再声明为virtual,即使被声明也会被编译器忽略。因此在C++的虚函数会有“跨层”,没有在父类中声明为virtual的函数可能是祖先的虚函数接口。
在C++11中为了帮助程序员写继承结构复杂的类型,引入虚函数描述符override,若派生类在虚函数声明时使用了override描述符,该函数必须重写其基类中的同名函数,负责代码无法通过编译。
struct Base
{
virtual void Turing() = 0;
virtual void Dijkstra() = 0;
virtual void VNeumann(int g) = 0;
virtual void DKnuth() const;
void Print();
};
struct DerivedMid : public Base
{
};
struct DerinedTop : public DerivedMid
{
void Turing() override;
void Djikstra() override; //拼写错误,编译错误
void VNeumann(double g) override; //参数类型错误,编译错误
void DKnuth() override ; //常量性错误,编译错误
void Print() override; //非虚函数重写错误,编译错误
};
如果没有override修饰符号,代码出错的可能性较大,且排查错误较为麻烦。override修饰符可以让编译器做一些辅助的检查。
总结
final/override在C++98的标准中,可以定义为正常变量名,只有final/override出现在函数后时才是能够控制继承和派生的关键字。此种设计对C++98的代码具有很好的兼容性。在c++11标准中尽量避免使用final/override作为变量名称。