noexcept
在C++11中,我们可以通过noexcept来指定某个函数不会抛出异常。
比如下面就是std::initializer_list的默认构造函数,其中使用了noexcept。
constexpr initializer_list() noexcept
: _M_array(0), _M_len(0) { }
该关键字告诉编译器,函数中不会发生异常,这有利于编译器对程序做更多的优化。
如果在运行时,noexecpt函数向外抛出了异常(如果函数内部捕捉了异常并完成处理,这种情况不算抛出异常),程序会直接终止,调用std::terminate()函数,该函数内部会调用std::abort()终止程序。
C++的异常处理
C++中的异常处理是在运行时而不是编译时检测的。为了实现运行时检测,编译器创建额外的代码,然而这会妨碍程序优化。
在实践中,一般两种异常抛出方式是常用的:
- 一个操作或者函数可能会抛出一个异常;
- 一个操作或者函数不可能抛出任何异常。
后面这一种方式中在以往的C++版本中常用throw()表示,在C++ 11中已经被noexcept代替。
void swap(Type& x, Type& y) throw() //C++11之前
{
x.swap(y);
}
void swap(Type& x, Type& y) noexcept //C++11
{
x.swap(y);
}
3 有条件的noexcecpt
在第2节中单独使用noexcept,表示其所限定的swap函数绝对不发生异常。然而,使用方式可以更加灵活,表明在一定条件下不发生异常。
void swap(Type& x, Type& y) noexcept(noexcept(x.swap(y))) //C++11
{
x.swap(y);
}
它表示,如果操作x.swap(y)不发生异常,那么函数swap(Type& x, Type& y)一定不发生异常。
一个更好的示例是std::pair中的移动分配函数(move assignment),它表明,如果类型T1和T2的移动分配(move assign)过程中不发生异常,那么该移动构造函数就不会发生异常。
pair& operator=(pair&& __p)
noexcept(__and_<is_nothrow_move_assignable<_T1>,
is_nothrow_move_assignable<_T2>>::value)
{
first = std::forward<first_type>(__p.first);
second = std::forward<second_type>(__p.second);
return *this;
}
什么时候该使用noexcept?
使用noexcept表明函数或操作不会发生异常,会给编译器更大的优化空间。然而,并不是加上noexcept就能提高效率,步子迈大了也容易扯着蛋。
以下情形鼓励使用noexcept:
- 移动构造函数(move constructor)
- 移动分配函数(move assignment)
- 析构函数(destructor)。这里提一句,在新版本的编译器中,析构函数是默认加上关键字noexcept的。
noexcept与虚函数
- 如果一个虚函数适用noexcept关键字其不会抛出异常,则后续派生出来的虚函数也必须做出同样的承诺;
- 如果基类的虚函数允许抛出异常,则派生类的对应函数既可以允许抛出异常,也可以不允许抛出异常(对函数本身做更严格的限定)。
void f(); //潜在抛出
void (*fn)() noexcept =ft; //错误
//若虚函数为不抛出,则所有声明,包括整个覆写的定义都必须是不抛出,除非覆写定义被删除。
struct B{
virtual void f() noexcept;
virtual void g();
virtual void h noexcept = delete;
};
struct D:B{
void f(); //错误,D::f为潜在抛出,B::f为不抛出
void g() noexcept; //正确
void h() = delete; //正确
};
//不抛出函数允许调用潜在抛出函数
void func1(); //潜在抛出
void func2() noexcept{
f(); //合法
throw 10; //合法,最终调用std::abort()终止程序
}
override
要求派生类的虚函数要覆盖掉基类中对应的虚函数(函数参数、返回类型必须相同)
关键字override修饰的方法必须和父类的名字、声明一致,如果有差异,编译期间会报错
class Base{
public:
Base(){}
~Base(){}
virtual void print(const char *str);
};
class A::public Base{
public:
A(){}
~A(){}
void print(const char* str);
};
但是上面的代码有一个缺点:如果派生类中想要重新定义的函数却不小心写错了,基类的函数没有被覆盖,但是编译器没有报错,使得调试的时候很难发现。
在C++11中提供的override关键字可以解决这一问题,它标记派生类中的虚函数,如果基类中的虚函数没有被覆盖,编译器则报错
final
假若一个函数被指定为final,则说明该函数不可被覆盖。
class A::public Base{
public:
A(){}
~A(){}
void print(const char* str) final;
};
class B:public A{
public:
A(){}
~A(){}
void print(const char* str);
//编译器报错,print无法被重写
};