C++ ——一文读懂:noexcept、override、final

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无法被重写
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

令狐少侠、

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值