开发者

What are guidelines of whether to throw an exception in C++ standard function?

I have found out that C++ standard functions show very different behavior when having an exception. This seem to contradict its touting of "try/throw/catch". Can anyone please briefly explain what are the C++ designer's reasoning behind these choices?

  1. Do nothing, for example, try to pop() a stack when it is empty (instead of throw a range_error), do sqrt(-1) (instead of throw a domain_error)

  2. Return a zero pointer: for example, when doing illegal pointer downcasting (interesting, doing an illegal reference downcasting will throw a bad_cast)

  3. Throw an exception, but this appear to a minority of fun开发者_JAVA技巧ctions, for example, substr()

  4. Give user a choice of whether to throw an exception, for example, new() will throw bad_alloc() when out of memory, but you can also choose (nothrow) as an option of new().


Most of the behaviour of C++ library functions can be explained by the general C++ philosophy "you don't pay for what you don't use". That means that any particular construction shouldn't incur any unneeded overhead when you use it correctly.

Optionally, more expensive, checked versions may exist, such as std::vector::at(), but it is up to you whether or not to use them.

The example of stack::pop() and sqrt() shows this philosophy in action: In order to throw an exception on error, you would always have to check whether the call is valid. This check is unnecessary if you already know that your call will succeed, so there is no mandatory check built into those functions. If you want a check, you can write one yourself.

The default new is slightly different, as it incorporates facilities for calling a new_handler, and so the checking is done anyway. (Recall that an exception is only expensive if you actually throw it, so that aspect isn't so important.) If you wanted to, you could always replace your own global operator new() by one which literally just forwards the argument to malloc(). (That would of course make it unsafe to use the default new expression, as you have no way of checking now that you can construct an object at the returned pointer. So you'll end up writing a check yourself and using placement-new, which is almost exactly what the nothrow-version does.)


Return a zero pointer: for example, when doing illegal pointer downcasting (interesting, doing an illegal reference downcasting will throw a bad_cast)

dynamic_cast provides an way to check the validity of the cast. It is by no means an exception as such.With pointers the cast returns a NULL because throwing an exception would be an overhead and the same can be achieved by returning a NULL, With the restriction that References cannot be NULL, there was no option but to throw an exception, there was no other way to return the result to user in this case.

Give user a choice of whether to throw an exception, for example, new() will throw bad_alloc() when out of memory, but you can also choose (nothrow) as an option of new().

Long ago new just returned NULL as in case of malloc, but later on it was standardized to throw an bad_alloc exception, This would have meant all the code previously written using new would have to be modified to a large extent to handle the exception, to avoid this and maintain a compatibility the nothrow version of new was introduced.


Pointer downcasts returning null is a simpler and faster way to test whether a given object is of the given subclass. I.e. you can write stuff like if (dynamic_cast<A*>(v) || dynamic_cast<B*>(v)), or if (A* a = dynamtic_cast<A*>(v)) doStuffWith(a);, which would've been cumbersome with exceptions. Here you actually expect casts to fail, while exceptions are exceptional by their nature, in that they are supposed to rarely be thrown during the normal execution of your program.

In other cases explicit checks for incorrect values may be omitted just for performance reasons. C++ is supposed to be efficient, instead of attempting to prevent one from shooting himself in the leg.


Most of this depends on whether there are enough appropriate return values to express the "failure" conditions and whether those values are convenient or not.

Rationale behind cited examples

std::stack<>::pop() does not throw an error because it doesn't have an error value and it simplifies calling code. It might not be an application logic error to attempt to pop an empty stack.

std::sqrt() has an appropriate value (not a number) which was included in floating point representations precisely for this purpose. It also has the benefit of propagating cleanly through other computations.

dynamic_cast<> on pointer types returns a null pointer to indicate a failed cast because the null pointer is already a standard way to represent "points to nothing". However, there is no such equivalent for references, so it must throw an exception as the return value cannot be used to indicate an error.

In contrast, std::string::substr() can't return an empty string to represent failure as an empty string is a valid sub-string of all strings.

new throwing std::bad_alloc or not seems to have historical roots, but, like dynamic_cast<> on pointers, it might be convenient for some code that would try alternatives not to pay for exception handling.


The c++ standard library is designed to be efficient, it avoids unnecessary runtime checks when possible.

1 Contains violations on the preconditions on very small/fast methods, checking these preconditions would most likely take longer than executing the methods themselves (pop is most likely a single decrement on a stack of simple types).

2 dynamic_cast checks and casts a given pointer to a compatible type. There is no separate way of only checking if a cast is possible since it would have to do the same work as the cast. Since c++ provides no separate way to only check if the cast is possible we have to expect that it may fail and it has a good error value to use when it fails (NULL). The reference version has to throw an exception as it cannot return an error value.

3 substr guarantees an exception, this may be for two reasons. One: the substr method is quite a bit more complex and slow than the methods mentioned in 1 and therefor the overhead of checking the precondition is negligible. Two: string processing is one of the biggest contributors to security holes as you are most likely processing user input, checking for overflows or out of bounds access is necessary to keep the process secure/stable. The c-library provides fast, unchecked and insecure methods to manipulate strings for those who need the speed.

4 new has to check whether it can return an address or fail in both cases, since running out of memory is unexpected by most applications the exception is reasonable. However you can write c++ while using a small subset of its features and many projects do not use exceptions as making your code exception safe is hard (especially if you use third party libraries which are not), since new is a central part of c++ an exception free implementation becomes necessary.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜