1.要重写时加上override,不想被重写或继承时加上final
相信稍微接触过JAVA
的程序员都知道上面的两个关键字是用来做什么的,在C++11
中其实也大同小异。
1.被加上final
关键字的结构体或者类不允许被继承
struct B1 final { };
struct D1 : B1 { }; // 错误!
2.被加上final
关键字的函数不允许被重写
struct B2
{
virtual void f() final {}
};
struct D2 : B2
{
virtual void f() {}// 错误!
};
3.被加上override
关键字的函数必须重写其父类的函数
struct B4
{
virtual void g(int) {}
};
struct D4 : B4
{
virtual void g(double) override {}// 错误!
};
上面的两个关键字不仅能帮我们发现和避免很多很难发现的程序错误,而且能确确实实地让编译器作出许多优化。比如标记了final
的virtual
函数将不会出现在虚表中,而标记为final
的类则根本不会生成虚表。
函数重写触发的条件:
- 要被重写的基类函数必须是虚函数;
- (除了析构函数)基类和派生类的函数名必须一致;
- 参数类型,返回值类型,异常声明,是否
const
等必须一致; - 对于
C++11
,还要求引用修饰子也必须一致
因此,override
并不是多此一举
,尤其对于C++
来说。
2.对于不允许或者不可能抛出异常的函数应该加上noexcept关键字
- 如果被
noexcept
修饰的函数抛出了异常,编译器可以选择直接调用std::terminate()
函数来终止程序的运行。 noexcept
比基于异常机制的throw()
在效率上会高一些,因为异常机制会带来一些额外开销,比如函数抛出异常,会导致函数栈被依次地展开(unwind
),并依帧调用在本帧中已构造的自动变量的析构函数等。noexcept
被广泛地、系统地应用在C++11
的标准库中,用于提高标准库的性能(尤其是move动作
、swap
、内存释放函数、析构函数),以及满足一些阻止异常扩散的需求。
3.使用constexpr来验证常量的初始值是否是在编译时就确定
- 被
constexpr
修饰的对象(包含基本类型)是常量,并且在编译时就可以确定其值。 - 被
constexpr
修饰的函数,在编译时能够根据传入的常数返回对应的常量。 - 被
constexpr
修饰的变量,只能用字面常量或者constexpr
函数的返回值初始化。
实例:
#include <iostream>
#include <array>
constexpr int pow(int base, int exp) noexcept {
int out = 1;
for(int i = 0; i < exp; i++) {
out *= base;
}
return out;
}
int main() {
std::array<int, pow(3, 3)> ary;
std::cout << ary.size() << std::endl; // 27
}
使用constexpr
的好处:
- 强制的语法约束,保证程序的正确语义;
- 编译器可以将
constexpr
表达式或者函数直接替换成结果字面量,同时避免了宏的额外开销
4.使用volatile关键字保证对特殊地址的稳定访问
如果某一个变量可能被某些编译器未知的因素更改(如:操作系统、硬件或者其他线程),那么最好使用volatile
关键字来修饰它,保证系统总是重新从它所在的内存读取数据。
一般来说,volatile
用在以下的几个地方:
- 中断服务程序中修改的供其他程序检测的变量;
- 多任务环境下各任务间共享的变量;
- 存储器映射的硬件寄存器
5.使用mutable关键字突破const的限制
- 被
mutable
修饰的变量将永远处于可变的状态,即使是在一个const
函数、const
结构体或者const
类对象中。 mutable
在类中只能修饰非静态数据成员。- 使用
const
修饰的函数或者类对象一般不能修改其状态,这种承诺一般是语义层面的,但编译器会强制进行严格的语法检查。尤其对于那些不会影响到结果的状态变量,其实可以放宽要求,这时候需要使用mutable
关键字了。