Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Transitioning to C++11 where destructors are implicitly declared with noexcept

In C++11, a destructor without any exception specification is implicitly declared with noexcept, which is a change from C++03. Therefore, a code which used to throw from destructors in C++03 would still compile fine in C++11, but will crash at runtime once it attempts throwing from such a destructor.

Since there's no compile-time error with such a code, how could it be safely transitioned to C++11, short of declaring all and every existing destructor in the code base as being noexcept(false), which would be really over-verbose and intrusive, or inspecting each and every destructor for being potentially throwing, which would be really time-consuming and error-prone to do, or catching and fixing all the crashes at runtime, which would never guarantee that all such cases are found?

like image 476
dragonroot Avatar asked Oct 05 '15 19:10

dragonroot


2 Answers

As 5gon12eder have mentioned, there are certain rules which result in a destructor without an exception specification to be implicitly declared as either noexcept or noexcept(false). If your destructor may throw and you leave it up to the compiler to decide its exception specification, you would be playing a roulette, because you are relying on the compiler's decision influenced by the ancestors and members of the class, and their ancestors and members recursively, which is too complex to track and is subject to change during the evolution of your code. Therefore, when defining a destructor with a body which may throw, and no exception specification, it must be explicitly declared as noexcept(false). On the other hand, if you are certain that the body may not throw, you may want to declare it noexcept to be more explicit and help the compiler optimize, but be careful if you choose to do this, because if a destructor of any member/ancestor of your class decides to throw, your code will abort at runtime.

Note that any implicitly defined destructors or destructors with empty bodies pose no problems. They are only implicitly noexcept if all destructors of all members and ancestors are noexcept as well.

The best way to proceed with the transition is therefore to find all destructors with non-empty bodies and no exception specifications and declare every one of them which may throw with noexcept(false). Note that you only need to check the body of the destructor - any immediate throws it does or any throws done by the functions it calls, recursively. There's no need to check destructors with empty bodies, destructors with an existing exception specification, or any implicitly defined destructors. In practice, there would not be that many of those left to be checked, as the prevalent use for those is simply freeing resources.

Since I'm answering to myself, that's exactly what I ended up doing in my case and it was not that painful after all.

like image 91
dragonroot Avatar answered Oct 01 '22 03:10

dragonroot


Note that the rules are not actually that brutal. The destructor will only be implicitly noexcept if an implicitly declared destructor would be. Therefore, marking at least one base class or member type as noexcept (false) will poison the noexceptness of the whole hierarchy / aggregate.

#include <type_traits>

struct bad_guy
{
  ~bad_guy() noexcept(false) { throw -1; }
};

static_assert(!std::is_nothrow_destructible<bad_guy>::value,
              "It was declared like that");

struct composing
{
  bad_guy member;
};

static_assert(!std::is_nothrow_destructible<composing>::value,
              "The implicity declared d'tor is not noexcept if a member's"
              " d'tor is not");

struct inheriting : bad_guy
{
  ~inheriting() { }
};

static_assert(!std::is_nothrow_destructible<inheriting>::value,
              "The d'tor is not implicitly noexcept if an implicitly"
              " declared d'tor wouldn't be.  An implicitly declared d'tor"
              " is not noexcept if a base d'tor is not.");

struct problematic
{
  ~problematic() { bad_guy {}; }
};

static_assert(std::is_nothrow_destructible<problematic>::value,
              "This is the (only) case you'll have to look for.");

Nevertheless, I agree with Chris Beck that you should get rid of your throwing destructors sooner or later. They can also make your C++98 program explode at the most inconvenient times.

like image 38
5gon12eder Avatar answered Oct 01 '22 02:10

5gon12eder