📜 ⬆️ ⬇️

The noexcept compile time specifier in C ++ 11

With the new C ++ standard, many interesting and useful improvements have appeared, one of which is the noexcept compile-time specifier, which tells the compiler that the function will not throw exceptions. If you are interested in the advantages of this specifier and are not afraid of C ++ code - welcome under cat.

Reducing the size of the binary file


Let's consider the following example, in which comments explain what the code is generated by the compiler:
#include <iostream> class my_class { int i_; public: explicit my_class (int i) : i_(i) {} int get() const { return i_; } }; inline int operator+(const my_class& v1, const my_class& v2) { return v1.get() + v2.get(); } int main() { int res = 0; try { //      var0  , //       res  -1 my_class var0(10); //      var1  , //       //  var0,     //   res  -1 my_class var1(100); //     , //       res  -1 //   var1  var0    , //     . res = (var1 + var0 > 0 ? 0 : 1); } catch (...) { std::cerr << " "; res = -1; } return res; } 

Too much code is generated by the compiler, isn't it? It is because of this swelling of the code that in some large corporations (we will not point fingers at Google ), when developing in C ++, the use of exceptions is prohibited. Another example is the development rules for GCC since version 4.8 (yes, GCC is now being developed using C ++, see changes for 4.8 ).

Let's modify the class my_class so that it uses noexcept and see what the compiler can generate:
 class my_class { int i_; public: explicit my_class (int i) noexcept : i_(i) {} int get() const noexcept { return i_; } }; inline int operator+(const my_class& v1, const my_class& v2) noexcept { return v1.get() + v2.get(); } int main() { // std::terminate   my_class var0(10); // std::terminate   my_class var1(100); // std::terminate   int res = (var1 + var0 > 0 ? 0 : 1); //   var1  var0 return res; } 


Note that std :: cerr is no longer used, and therefore the compiler will be able to remove a very large piece of code. But even without this, the amount of generated code has become less.
')
Unfortunately, we looked at the perfect compiler. In practice, things may not be so great: the feature is quite new, not all compilers are able to use it well for optimizations.
Also, some compilers sometimes and without noexcept can determine if a class or method will throw exceptions.

Here are the results of compiling different versions of the same class on GCC 4.7.2:
1) The initial class, the body of main from the first variant, the flags -std = c ++ 0x -fno-inline -O2: 5280 bytes
2) Optimization class, main body from the second variant, flags -std = c ++ 0x -fno-inline -O2: 4800 bytes

3) Optimization class, main body from the first variant, flags -std = c ++ 0x -fno-inline -O2: 5280 bytes
4) Optimization class, main body from the first variant without iostream, flags -std = c ++ 0x -fno-inline -O2: 4800 bytes
5) Optimization class, main body from the second option + iostream is turned on, -std = c ++ 0x -fno-inline -O2 flags: 5280 bytes

* The -std = c ++ 0x flag includes the features of C ++ 11, which is required to use noexcept.
** When repeating the experiment, do not forget to remove the debug information from the binary file.

Comparing the first two lines we get the expected size improvement in the case of a perfect compiler.
The third, fourth and fifth lines show that the compiler and without noexcept could be optimized to our second option, and only could not throw out the extra declarations used in the iostream header file.

Let's cheat a little, and we will spread the method main () and the implementation of my_class on different files, so that the compiler is more difficult to optimize.
Results:
The initial class, the body of the main from the first option, the flags -std = c ++ 0x -fno-inline -O2: 6952 bytes
Optimization class, main body from the first variant, -std = c ++ 0x -fno-inline -O2 flags: 5288 bytes

Total: a gain of 23.9% after adding three noexcept.

Acceleration of standard algorithms


For many people using C ++ 11, this part of the N3050 standard may be a big surprise (at the moment this standard is not implemented in all leading compilers). If in short, it says that standard algorithms and containers should not use move assignment and move construction if these methods can throw exceptions. Than it can threaten we consider on an example:
 //       move assignment class move_fast_copy_slow { //      public: move_fast_copy_slow(move_fast_copy_slow&&); // 1 move_fast_copy_slow(const move_fast_copy_slow&); // 2 move_fast_copy_slow& operator=(move_fast_copy_slow&&); // 3 move_fast_copy_slow& operator=(const move_fast_copy_slow&); // 4 }; 


If methods 1 and 3 are not marked with noexcept, then standard containers will use the slower methods 2 and 4. As a result, working with containers std :: vector and std :: deque can slow down by a couple of orders of magnitude. In addition, this slowdown will affect all types inherited or using move_fast_copy_slow as a member.

Compatibility


At the time of this writing, not all leading compilers support noexcept. Use the BOOST_NOEXCEPT macro from the boost library instead of noexcept for code portability.

Underwater rocks


By standard, noexcept is not part of the function type, but when using virtual functions, all overloaded functions should have the same or more stringent exception specification. That is, the following code will not be collected:
 class base { public: virtual void foo() noexcept {} }; class derived: public base { public: void foo() override { throw 1; } }; int main () { derived d; base *p = &d; p->foo(); } 

There will be an error like "error: looser throw specifier for virtual void derived :: foo ()".
So be prepared for the fact that if you are the author of the library and added the noexcept specification to the proprietary function, the code will no longer be collected from the users of your library.
Also be prepared for the fact that your exception classes with the overloaded method what () and inherited from the standard ones may not compile if they are not marked with noexcept:
 class my_exception : public std::exception { const char* what() throw() { //  ++11   noexcept  throw() return "my_exception::what()"; } }; 

Conclusion


The noexcept compile time specifier greatly reduces the size of the final file and speeds up the program. The main thing when using noexcept is not to overdo it. Remember that if a function marked with noexcept throws an exception out, then your program will call std :: terminate () and exit, without even deigning to call destructors for already created variables.

Source: https://habr.com/ru/post/164221/


All Articles