throw()
or to Throw: Nothing.Section: 16 [library] Status: NAD Submitter: Martin Sebor Opened: 2008-08-23 Last modified: 2018-06-23
Priority: Not Prioritized
View other active issues in [library].
View all other issues in [library].
View all issues with NAD status.
Discussion:
Recent changes to
the working
draft have introduced a gratuitous inconsistency with the C++ 2003
version of the specification with respect to exception guarantees
provided by standard functions. While the C++ 2003 standard
consistenly uses the empty exception specification, throw()
,
to declare functions that are guaranteed not to throw exceptions, the
current working draft contains a number of "Throws: Nothing."
clause to specify essentially the same requirement. The difference
between the two approaches is that the former specifies the behavior
of programs that violate the requirement (std::unexpected()
is called) while the latter leaves the behavior undefined.
A survey of the working draft reveals that there are a total of 209
occurrences of throw()
in the library portion of the spec,
the majority in clause 18, a couple (literally) in 19, a handful in
20, a bunch in 22, four in 24, one in 27, and about a dozen in D.9.
There are also 203 occurrences of "Throws: Nothing." scattered throughout the spec.
While sometimes there are good reasons to use the "Throws:
Nothing." approach rather than making use of throw()
, these
reasons do not apply in most of the cases where this new clause has
been introduced and the empty exception specification would be a
better approach.
First, functions declared with the empty exception specification
permit compilers to generate better code for calls to such
functions. In some cases, the compiler might even be able to eliminate
whole chunks of user-written code when instantiating a generic
template on a type whose operations invoked from the template
specialization are known not to throw. The prototypical example are
the std::uninitialized_copy()
and std::uninitialized_fill()
algorithms where the
entire catch(...)
block can be optimized away.
For example, given the following definition of
the std::uninitialized_copy
function template and a
user-defined type SomeType
:
template <class InputIterator, class ForwardIterator> ForwardIterator uninitialized_copy (InputIterator first, InputIterator last, ForwardIterator res) { typedef iterator_traits<ForwardIterator>::value_type ValueType; ForwardIterator start = res; try { for (; first != last; ++first, ++res) ::new (&*res) ValueType (*first); } catch (...) { for (; start != res; --start) (&*start)->~ValueType (); throw; } return res; } struct SomeType { SomeType (const SomeType&) throw (); }
compilers are able to emit the following efficient specialization
of std::uninitialized_copy<const SomeType*, SomeType*>
(note that the catch
block has been optimized away):
template <> SomeType* uninitialized_copy (const SomeType *first, const SomeType *last, SomeType *res) { for (; first != last; ++first, ++res) ::new (res) SomeType (*first); return res; }
Another general example is default constructors which, when decorated
with throw()
, allow the compiler to eliminate the
implicit try
and catch
blocks that it otherwise must
emit around each the invocation of the constructor
in new-expressions.
For example, given the following definitions of
class MayThrow
and WontThrow
and the two
statements below:
struct MayThrow { MayThrow (); }; struct WontThrow { WontThrow () throw (); }; MayThrow *a = new MayThrow [N]; WontThrow *b = new WontThrow [N];
the compiler generates the following code for the first statement:
MayThrow *a; { MayThrow *first = operator new[] (N * sizeof (*a)); MayThrow *last = first + N; MayThrow *next = first; try { for ( ; next != last; ++next) new (next) MayThrow; } catch (...) { for ( ; first != first; --next) next->~MayThrow (); operator delete[] (first); throw; } a = first; }
but it is can generate much more compact code for the second statement:
WontThrow *b = operator new[] (N * sizeof (*b)); WontThrow *last = b + N; for (WontThrow *next = b; next != last; ++next) new (next) WontThrow;
Second, in order for users to get the maximum benefit out of the new
std::has_nothrow_xxx
traits when using standard library types
it will be important for implementations to decorate all non throwing
copy constructors and assignment operators with throw()
. Note
that while an optimizer may be able to tell whether a function without
an explicit exception specification can throw or not based on its
definition, it can only do so when it can see the source code of the
definition. When it can't it must assume that the function may
throw. To prevent violating the One Definition Rule,
the std::has_nothrow_xxx
trait must return the most
pessimistic guess across all translation units in the program, meaning
that std::has_nothrow_xxx<T>::value
must evaluate to
false
for any T
whose xxx
(where xxx
is default or copy ctor, or assignment operator)
is defined out-of-line.
Counterarguments:
During the discussion of this issue
on lib@lists.isocpp.org
(starting with post c++std-lib-21950
) the following arguments
in favor of the "Throws: Nothing." style have been made.
throw()
even if they don't actually throw). This is a
common situation when the called function is a C or POSIX function.
__attribute__((nothrow))
or Visual
C++ __declspec(nothrow)
)
that let implementers mark up non-throwing functions, often without
the penalty mentioned in (1) above. The C++ standard shouldn't
preclude the use of these potentially more efficient mechanisms.
throw()
. Declaring such functions with the empty
exception specification will cause compilers to generate suboptimal
code when the user-defined function isn't also declared not to throw.
The answer to point (1) above is that implementers can (and some have)
declare functions with throw()
to indicate to the compiler
that calls to the function can safely be assumed not to throw in order
to allow it to generate efficient code at the call site without also
having to define the functions the same way and causing the compiler
to generate suboptimal code for the function definition. That is, the
function is declared with throw()
in a header but it's
defined without it in the source file. The throw()
declaration is suppressed when compiling the definition to avoid
compiler errors. This technique, while strictly speaking no permitted
by the language, is safe and has been employed in practice. For
example, the GNU C library takes this approach. Microsoft Visual C++
takes a similar approach by simply assuming that no function with C
language linkage can throw an exception unless it's explicitly
declared to do so using the language extension throw(...)
.
Our answer to point (2) above is that there is no existing practice where C++ Standard Library implementers have opted to make use of the proprietary mechanisms to declare functions that don't throw. The language provides a mechanism specifically designed for this purpose. Avoiding its use in the specification itself in favor of proprietary mechanisms defeats the purpose of the feature. In addition, making use of the empty exception specification inconsistently, in some areas of the standard, while conspicuously avoiding it and making use of the "Throws: Nothing." form in others is confusing to users.
The answer to point (3) is simply to exercise caution when declaring functions and especially function templates with the empty exception specification. Functions that required not to throw but that may call back into user code are poor candidates for the empty exception specification and should instead be specified using "Throws: Nothing." clause.
[ 2009-07 Frankfurt ]
We need someone to do an extensive review.
NAD Future.
[2017-02 in Kona, LEWG recommends NAD]
The discussed discrepancy isn't relevant any longer: now we have noexcept and have deprecated throw(). Additionally, the guidance on narrow vs. wide contracts, Requires clauses, and noexcept/Throws means that the proposed resolution is more subtle even if updated in terms of noexcept().
[2017-06-02 Issues Telecon]
Resolve as NAD
Proposed resolution:
We propose two possible solutions. Our recommendation is to adopt Option 1 below.
Option 1:
Except for functions or function templates that make calls back to
user-defined functions that may not be declared throw()
replace all occurrences of the "Throws: Nothing." clause with
the empty exception specification. Functions that are required not to
throw but that make calls back to user code should be specified to
"Throw: Nothing."
Option 2:
For consistency, replace all occurrences of the empty exception specification with a "Throws: Nothing." clause.