In a previous post, we had a look at the new constexpr keyword that has been introduced in C++ 11. Today we'll study another new fancy specifier: noexcept.
C++ is all about specifiers. Every time you add a specifier to a method, the ISO committee makes 1 cent.
Exception specifications in C++ 98
In C++ 03 you could specify that code was not supposed to throw any exception as such:
void my_function() throw()
{
// one does not simply throw
}
Sounds awesome right? Except it's not. When the exception is thrown, the program will terminate. In C++ 98, the stack will be unwound. Not only you don't have any useful feedback, but there is no optimization opportunity for the compiler.
You could also specify that a function throws specific exceptions, but it wasn't so useful: many compilers didn't implement that correctly and the idea is terrible in the first place.
TL;DR: Exceptions specification is useless in C++ 98: don't bother
Exception specification since C++ 11
In C++ 11 two things changed in exception specification:
- You either throw or you don't
- The compiler doesn't have to unwind the stack before terminating the program
- (bonus) You can query the exception safety of an expression at compile-time
Forget about throw
, the noexcept
specifier is your new friend.
TL;DR: The new exception specification syntax will not interfere with the previous one and is easier to understand. Use it.
The compiler does not check anything
This may be surprising, but this code will compile without an error:
void my_function() noexcept
{
throw std::runtime_error("lolilol");
}
If the compiler were to check functions, this would prevent you from using unspecified code, such as C functions.
TL;DR You are solely responsible of the accuracy of the
noexcept
specification.
If you throw, your program terminates
What happens if you throw from a noexcept
function? Your program terminates, and may or may not unwind the stack.
Terminating program isn't the nicest way to report an error to your user.
You would be surprised that most of C++ code might throw. For example, every time your code allocates memory, there is a possibility for a std::bad_alloc
exception to be thrown.
Every function you call, every object you instantiate is an opportunity for an exception to be thrown. noexcept
means the code never throws. It is not about probabilities.
TL;DR Tread lightly when using
noexcept
.
Actual optimization opportunities
When the compiler encounters the noexcept
keyword, it will assume your code never throws.
This means it can replace this code:
void my_function() noexcept {}
try
{
my_function();
}
catch(const std::exception & e)
{
std::cerr << e.what() << std::endl;
}
with this code:
void my_function() noexcept {}
my_function();
That's for the most obvious optimization. There are more advanced optimizations enabled with noexcept
, such as the conditional moves.
TL;DR
noexcept
gives real optimization opportunities to the compiler and may thus be worth the effort.
Try not to be stupid
"Faster code with noexcept
? Understood! Behold my optimized code!"
void i_am_so_clever(void) noexcept
{
try
{
function_which_throws();
}
catch(...)
{
// ah! ah! to me the optimizations!
}
}
There is no point in writing such code. It's more code, it's actually slower and it's harder to understand. With C++, be straight to the point. There are other languages out there if you like to over-engineer.
TL;DR: If your function throws, let it be and don't try to make it
noexcept
.
You can infer if a function is noexcept
Imagine this case:
template <typename T, typename Params...>
T awesome_factory(Params... params)
{
return T{params...};
}
You would like to write "if my constructor is noexcept, then I would like my awesome factory to be noexcept.".
First, some more information about what the noexcept
specifier actually is. When you write:
void f() noexcept
{
// ...
}
You are implicitly writing:
void f() noexcept(true)
{
}
Lucky you, there is a noexcept
operator which returns true when an expression is declared to not throw an exception.
With that knowledge in mind, combining the noexcept
specifier with the noexcept
operator is a solution to our problem:
template <typename T, typename Params...>
T awesome_factory(Params... params) noexcept(noexcept(T{params...}))
{
return T{params...};
}
TL;DR: Whenever possible, infer if an expression is
noexcept
with thenoexcept
operator.
noexcept by default
It is worth noting that these expressions are noexcept
by default:
- Destructors
- Deallocation functions
You could declare them as potentially throwing function (with noexcept(false)
), but remember that throwing from a destructor results in undefined behavior.
noexcept is a binding contract
The noexcept
not only conveys information to the compiler, it also conveys information to the developer.
That means that not only the compiler will optimize, but most likely the developer using your interface will make assumption based on your specifiers.
You are saying "I am certain this code will never throw exceptions". Once you offer that guarantee, removing it can break a lot of code.
TL;DR: When using
noexcept
you must not only be certain your code does not throw, but that it will very likely never throw in future implementations. When in doubt, abstain.
When noexcept?
At quasardb we have a rule of thumb for noexcept
.
If your expression recursively meets the following criteria:
- Doesn't explicitly throw an exception
- Only constructs trivial types or types with
noexcept
constructors - Calls only
noexcept
or C functions (careful with operators!) - Will likely stay as such in the future
Then it is a good noexcept
candidate.
When creating an object, it's worth looking at it to see if its constructors and assignment operators can be specified as noexcept
:
- By induction you make it possible to spread exception safety information throughout your code.
- You give real optimization opportunities to the compiler when your objects are used in containers.
Are you looking for a software engineering internship? Look no further! We have available positions!