文档结构  
翻译进度:已翻译     翻译赏金:0 元 (?)    ¥ 我要打赏

在之前的帖子中,我们谈到了在c++11版本中介绍的新关键字constexpr。今天我们会研究另一个有意思的说明符:noexcept。

c++在很多时候依赖于说明符。每次你为你的方法加上一个说明符,ISO委员会就会挣一分钱。

 C++ 98 中的异常处理规范

在C++ 03 你可以指定你的代码不会抛出任何异常,就像下面这样:

void my_function() throw()  
{
     // one does not simply throw
}

看上去很不错对吗?然而并不是。当异常被抛出的时候,程序会终止。在C++98中,.堆栈将被清除。你不但得不到任何有用的反馈信息,有没有给编译器优化的机会。

第 1 段(可获 1.41 积分)

你也可以指定函数抛出特定的异常,但它不是那么有用:很多编译器没有正确的实现它,并且这个想法在一开始就是错误的。

总而言之: 异常规范在c++98中是无用的,别费功夫了。

从C++11后的异常规范

在C++ 11 异常规范中有两个东西被改变了:

  1. 你要么抛出要么不抛出异常
  2. 编译器在终止程序之前不会清除堆栈
  3. (额外的好处) 你可以在编译期查询一个表达式的异常安全性

忘掉throw吧,noexcept说明符是你的新朋友。

第 2 段(可获 1.23 积分)

精简版总结: 新的异常规范语法不会和之前的有所冲突并且很容易理解。试着使用它。

编译器不会检查任何东西

这可能会让你惊讶,但是这段代码会成功编译不报错:

void my_function() noexcept  
{
   throw std::runtime_error("lolilol");
}

如果编译器检查函数,这将阻止你使用未指定的代码,如C函数。

精简版总结: 你只用对 noexcept 规范的准确性负责.

如果你抛出异常,程序则会终止

如果你从一个 noexcept 函数中抛出异常会怎么样? 你的程序会终止,有可能会也有可能不会清除堆栈。

第 3 段(可获 1.19 积分)

终止程序不是向你的用户报告错误的最好的做法。

你可能会惊讶大多数的C++代码确实可能抛出异常.。例如,每当你的代码申请内存的时候,有可能会抛出一个std::bad_alloc异常。

每个你调用的函数,或者每个你实例化的对象,都存在者抛出异常的可能性。noexcept 意味着代码绝不抛出异常。这无关概率。

精简版总结: 当使用 noexcept时要小心。

实际的优化机会

当编译器碰到noexcept 关键字的时候,它会假设你的代码不会有异常。

第 4 段(可获 1.15 积分)

这意味着你可以使用下下一段代码代替下一段的代码

void my_function() noexcept {}

try  
{
   my_function();
}
catch(const std::exception & e)  
{
   std::cerr << e.what() << std::endl;
}

 

void my_function() noexcept {}

my_function();  

这是最为明显的优化的地方。还可以使用noexcept进行更高级的优化,例如conditional moves.

精简版总结: noexcept 给了编译器真正的优化机会,因为是可能值得努力使用的。

试着别做傻事

"使用noexcept使得代码更加快?了解!看我的优化代码!"(译注:应该是警告你别滥用noexcept 

第 5 段(可获 0.73 积分)
void i_am_so_clever(void) noexcept  
{
   try
   {
      function_which_throws();
   }
   catch(...)
   {
       // ah! ah! to me the optimizations!
   }
}

写这样的代码是没有意义的。所增加的代码实际上使得程序更慢更加难以理解了。使用c++的时候就要直切要害。如果你喜欢过度工程化就去使用别的语言。

精简版总结:如果你的函数抛出异常了,就任由它去别试着强行noexcept。

你可以推断一个函数是否是noexcept的

试想这个例子:

template <typename T, typename Params...>  
T awesome_factory(Params... params)  
{
    return T{params...};
}
第 6 段(可获 0.84 积分)

你可能想写"如果我的构造器是noexcept的,那么我想要我漂亮的工厂方法也是noexcept的".

首先,有一些信息可以表明noexcept 说明付实际包含的意思。当你写下:

void f() noexcept  
{
   // ...
}

你实际上是写了:

void f() noexcept(true)  
{

}

对你来说很辛运的是,当一个表达式被声明不抛出异常时,一个noexcept 操作符会返回true。

知道了这个,那么结合noexcept 说明符和noexcept 操作符就可以成为你问题的解决方案:

template <typename T, typename Params...>  
T awesome_factory(Params... params) noexcept(noexcept(T{params...}))  
{
   return T{params...};
}
第 7 段(可获 0.91 积分)

精简版总结: 只要有机会,尽可能的使用noexcept 操作符来推断一个表达式是不是noexcept 的。

默认noexcept 

值得注意的是下面这些表达式是默认noexcept 的。

  1. 析构函数
  2. 释放内存函数

你可以潜在的声明它们为一个异常抛出函数(带有noexcept(false)),但是要记住从一个析构函数抛出异常是未定义行为。

noexcept 是一个约束合同

noexcept 不仅向编译器传递信息,也向开发者传递信息。

那意味着不仅编译器会做优化,而且很有可能开发者会作基于你说明符的假设来使用你的接口。

第 8 段(可获 1.15 积分)

你得说:“我非常肯定这段代码绝不会抛出异常”。一旦你提供了这个保证,再移除它会破坏很多其他的代码。

精简版总结:当你使用noexcept 时你不仅要确认你的代码不会抛出异常,而且在将来的实现过程中也很可能不会抛出异常。当你对此存疑时,要放弃使用。

什么时候用noexcept?

在 quasardb 上我们有几条经验法则:

如果你的表达式依次满足以下几条规则:

  • 不会显式的抛出一个异常
  • 平凡类型的构造器或者带有noexcept 构造器的类型
  • 满足noexcept 的函数调用或者C函数(主意操作符!)
  • 在将来很可能保持现状
第 9 段(可获 1.3 积分)

那么它就是可以 选择使用noexcept 的。

当产生一个对象的时候,是值得你去查看它的构造器的赋值操作符能够被声明为noexcept:

  1.  通过归纳你可以通过你的代码传递异常安全信息。
  2. 当你的对象被使用在容器当中时,你给了编译器真正的优化机会。
第 10 段(可获 0.71 积分)

文章评论