1. override和final简单概括
override和final是C++11中的新特性,这两个新特性可以让我们在继承和重写虚函数时更加安全。
2. override代码实例
我们总会遇到这种情况:在子类中,本来想重写虚函数,结果虚函数却没有被正确地调用。
或者更惨的是,你有时不得不去修改父类虚函数的声明。在所有的子类中查找重载的函数这件事可真的是很麻烦。那么看代码:
#include <iostream>
using namespace std;
struct Base
{
virtual void doSomething(int i) const
{
cout << "This is from Base with " << i << endl;
}
};
struct Derivied : Base
{
virtual void doSomething(int i)
{
cout << "This is from Derived with " << i << endl;
}
};
void letDoSomething(Base& base)
{
base.doSomething(419);
}
int main()
{
Derivied d;
letDoSomething(d); //输出结果: "This is from Base with 419"
return 0;
}
通过观察这个例子我们发现,子类并没有重写父类的虚函数,Derived::doSomething函数把Base::doSomething的const给搞丢了。所以他们两者并没有相同的函数签名,前者也没有如我们预想的对后者进行重写。确实会有编译器弹出warning提示这个问题,但是往往在我们本意就不想重写虚函数时它也会报警——久而久之我们就麻木了,编译器并不能从根本上解决这个问题。像这样的场景,我们也没有工具来区分告诉编译器我们的本意是否想重写这个父类的虚函数。因此,在C++11中,我们引入了override这个关键字。
代码如下:
- 如果我们需要重写父类的虚函数,那么在最后加上override,编译器会帮我们判断是否真正重写了虚函数
struct Derived : public Base
{
// ERROR,编译器报警没有正确重写虚函数,应该是 void doSomething(int i) const override {}
void doSomething(int i) override
{
std::cout << "This is from Derived with " << i << std::endl;
}
};
- 下面是正确重写虚函数代码:
#include <iostream>
using namespace std;
struct Base
{
virtual void doSomething(int i) const
{
cout << "This is from Base with " << i << endl;
}
};
struct Derivied : Base
{
void doSomething(int i) const override
{
cout << "This is from Derived with " << i << endl;
}
};
void letDoSomething(Base& base)
{
base.doSomething(419);
}
int main()
{
Derivied d;
letDoSomething(d); //输出结果: "This is from Base with 419"
return 0;
}
很简单,加个关键字,就可以让编译器来检查我们又没有正确重写父类的虚函数。因此,任何子类重写虚函数后导致函数签名的变化,都会导致编译器报错。
除此之外,如果你一直使用override,他还会给你带来一个意想不到的收获:在C++11之前,关于子类重写父类虚函数后,子类的虚函数还要不要加virtual关键字,还是个值得争论的问题。人们一开始之所以想在子类的重写函数上也加上virtual,就是为了提醒读代码的人这是个重写的虚函数。但是在有了override之后,这个关键字本身就起了这个作用,之前的做法也就不是必须的了。所以建议的做法是,在最顶层的虚函数上加上virtual关键字后,其余的子类重写后就不再加virtual了,但是要统一加上override。
3. 防止重写
针对上面特性的反面,C++11也讨论了如何防止子类再覆写父类的虚函数了——即父类的某些特性或方法被设计成不想再被改变。在C++11出现前,比较难受的一点就在于,即使父类的虚函数是private的情况下,我们仍然无法阻止它的子类覆写这个方法。
#include <iostream>
using namespace std;
class Base
{
public:
void doSomething() const
{
cout << "I did something" << dontChangeMe() << endl;
}
private:
virtual int dontChangeMe() const = 0;
};
class ChildOfBase : public Base
{
private:
int dontChangeMe() const override
{
return 419;
}
};
class BadChildOfChild : public ChildOfBase
{
int dontChangeMe() const override
{
return 61;
}
};
int main()
{
BadChildOfChild badLittleChild;
badLittleChild.doSomething(); //结果是61
}
在C++11之前,你还对上边的行为无可奈何,至少你无法在语法层面去禁止开发人员这么做。现在,我们有了final关键字来帮我们这个忙。
class ChildOfBase : public Base
{
private:
int dontChangeMe() const final
{
return 419;
}
};
class BadChildOfChild : public ChildOfBase
{
int dontChangeMe() const override//报错:无法重写final函数
{
return 61;
}
};
关于final和override关键字位置的一个小提示:
1、这两者应放在const,volatile等其他关键字后边,但是应该在纯虚标记,也就是"=0"的前边。
2、一个final的纯虚函数是没什么意义的,因为本身就是一个抽象函数又不让后边的子类覆写给与它实际意义,这就很无聊。
3、另外就是override final和final override并没有什么区别,只是后者读起来可能更顺一些吧。只写一个final并不会像override那样检查覆写类型,所以最好还是两个都写上。
4、在ChildOfBase类的dontChangeMe函数后面加上final后,再去重写ChildOfBase类的dontChangeMe函数就不可能了,但是写一个新的子类继承Base,再重写dontChangeMe还是允许的。
4. final修饰类
final还有第二种用法,直接用在类上,紧跟着类名,表示这个类禁止任何其他类继承它,无论是public继承还是private继承。
class Base final
{
......
};
class ChildOfBase : public Base//报错:不能将final类作为基类
{
......
};
但是在给类使用final之前,还是要想清楚final是否真的是必要的,如果不是,那它就是一个**坑**。
总结
override 和 final 都能帮助我们改善虚函数相关的安全性;
而使用final需要更多的权衡,但是override就放心大胆地用吧。