The really scary part of noexcept

Hi,

There’s one small piece of the noexcept specification left to implement. It’s not only scary, it’s also fiendishly difficult to find, being the only part of noexcept that isn’t within [except]. [class.dtor]p3 says:

“A declaration of a destructor that does not have an exception-specification is implicitly considered to have the same exception-specification as an implicit declaration.”

This means that destructors suddenly grow exception specifications, and they may be really, really wrong. Consider this little class, which could be used as an RAII guard.

class TransactionGuard {
DatabaseConnection& m_conn;
public:
TransactionGuard(DatabaseConnection& conn) : m_conn(conn) {}
~TransactionGuard() {
if (std::unhandled_exception()) { // if we’re being destructed due to an exception being thrown
try { conn.rollback(); } catch(…) {} // rollback and swallow any errors, or we terminate
} else {
conn.commit(); // otherwise commit, and let exceptions escape
}
}
};

The above destructor would suddenly grow an exception-specification. Specifically, because it has no object data members, the exception-specification would be empty: it would be noexcept(true). If the conn.commit() call throws, the program would immediately terminate instead of letting the exception escape.

Scary? Hell, yes! Useful? That too, when you consider that most constructors don’t throw.

Now, the rationale for this part of the standard was that throwing destructors are high-risk parts of a program, so programmers would know where they are and check them when upgrading to C++0x. This argument makes sense - if you consider the C++0x transition global and instantaneous. But in reality, with compilers adopting different parts of the standard in different order, it’s a headache.

So my question is, how should we implement this in Clang? Simply put it under C++0x? Have it enabled in C++0x by default, but add a switch to turn it off? Add an explicit switch to turn it on?

Sebastian

Hello,

The above destructor would suddenly grow an exception-specification. Specifically, because it has no object data members, the exception-specification would be empty: it would be noexcept(true). If the conn.commit() call throws, the program would immediately terminate instead of letting the exception escape.

Scary? Hell, yes! Useful? That too, when you consider that most constructors don't throw.

Don't you mean "destructors"? After all, that's what we're talking about.

Now, the rationale for this part of the standard was that throwing destructors are high-risk parts of a program, so programmers would know where they are and check them when upgrading to C++0x. This argument makes sense - if you consider the C++0x transition global and instantaneous. But in reality, with compilers adopting different parts of the standard in different order, it's a headache.

So my question is, how should we implement this in Clang? Simply put it under C++0x? Have it enabled in C++0x by default, but add a switch to turn it off? Add an explicit switch to turn it on?

The last option seems wrong, as it would mean that using only -std=c++0x would not result in standard-conformity. Adding a switch
to turn it off would be useful for backwards compatibility, but again would break standards compliance. So I would run with the first
option: The implicit "noexcept(true)" is part of the C++0x standard, therefore it should be enabled in C++0x by default.

I think a warning could be useful, though, which would trigger when a destructor does not have an *explicit* noexcept specifier. This
warning would then serve as a reminder to check the destructor(s) in question.

Jonathan

Sebastian Redl wrote:

Hi,

There's one small piece of the noexcept specification left to implement.
It's not only scary, it's also fiendishly difficult to find, being the
only part of noexcept that isn't within [except]. [class.dtor]p3 says:

"A declaration of a destructor that does not have an
exception-specification is implicitly considered to have the same
exception-specification as an implicit declaration."

This means that destructors suddenly grow exception specifications, and
they may be really, really wrong. Consider this little class, which could
be used as an RAII guard.

class TransactionGuard {
  DatabaseConnection& m_conn;
public:
  TransactionGuard(DatabaseConnection& conn) : m_conn(conn) {}
  ~TransactionGuard() {
    if (std::unhandled_exception()) { // if we're being destructed due to
    an exception being thrown
      try { conn.rollback(); } catch(...) {} // rollback and swallow any
      errors, or we terminate
    } else {
      conn.commit(); // otherwise commit, and let exceptions escape
    }
  }
};

The above destructor would suddenly grow an exception-specification.
Specifically, because it has no object data members, the
exception-specification would be empty: it would be noexcept(true). If the
conn.commit() call throws, the program would immediately terminate instead
of letting the exception escape.

[...]

So my question is, how should we implement this in Clang? Simply put it
under C++0x? Have it enabled in C++0x by default, but add a switch to turn
it off? Add an explicit switch to turn it on?

In my opinion, the situation for a program that does not use
std::uncaught_exception() to check whether there currently is an unhandled
exception is not worsened by this, because if the exception is thrown, it
already has the risk of having terminate called.

So I wonder whether the following makes sense:

- Implement it according to the spec, and when you process the body:
- When the destructor does not have an explicit exception specification but
a nonthrowing implicit exception specification, and
- when the body contains a call to std::uncaught_exception

If the two conditions are satisfied, issue a warning that
terminate/unexpected will be called regardless of the return value of
std::uncaught_exception() when an exception escapes the body.

I don't quite understand whether the exception specification will be
"throw()" or "noexcept(true)". It seems to make a difference wrt whether
unexpected() or teminate() is called. Subsection 15.4 hasn't enlighted me:
It just says that the function disallows any exception.

Yes, I consider this a defect in the current draft (I should report it), but given that dynamic specs are deprecated in C++0x, I made it be equivalent to noexcept(true) in Clang for C++0x.

Sebastian