"The
(B)Leading Edge"
Guidelines for Throwing Exceptions
by
Jack Reeves
©1996 - The C++ Report
February 28, 1996
Welcome to the first installment of "The (B)Leading Edge." This is a column about Standard C++; as in "The ANSI/ISO Standard". Obviously, a definition of what is the leading edge in any programming language is very much a matter of opinion and circumstances. I have no intention of trying to come up with a precise definition, but it is probably safe to say that if a C++ language feature has been mentioned by Josée Lajoie or Mike Vilot in an article in The C++ Report in the last couple of years, then I will be looking at it.
More seriously, as the draft C++ standard shifts from being a moving target to just being a target, more information about what is in the Standard is making its way into the hands of mainstream software developers. As it does so, these people are discovering that Standard C++ contains a richness of additional features and libraries that they probably have not had access to. They are also going to discover that there are some things in Standard C++ that are not going to work the way they probably do in their current environment. In either case, sooner are later they are going to want to try out some of the "new and improved" features of Standard C++. More than likely, they will have some problems and discover that the leading edge can sometimes be rather sharp. I am going to try to explore some of those edges in this column and together we will try to figure out where the leading stops and the bleeding starts.
There is a hidden double entendre in the title of this column that I feel compelled to mention. The C++ Standard is still a draft. That means it is still subject to interpretation, clarification, and even change. In this column, the latest version of the standard will be the reference [1]. I may often show code examples that I believe are valid but which I can not test on any compiler that I have. My interpretations may be reasonable but not correct. Finally, what I say might be correct when I write it, but may be changed by the committee later. This is my own (b)leading edge.
With that said, let me return to one of my favorite topics: exceptions. It has been said that C++ is a big and complex programming language with a smaller, simpler language inside, struggling to escape. There is a lot of truth in this. C++ supports many different programming styles, but does not enforce any one in particular. Therefore the programmer is usually free to work with some subset of the language that she is comfortable with and ignore the rest -- C++ really can be used as just a better C. Exceptions are a problem, however. Standard C++ is an exception based language -- exceptions are part of the language and are used by the libraries. When any part of a program can throw an exception, then the entire program must be coded in an exception aware manner. I caveat the above statement with one qualification: if you never catch any exceptions other than to exit the program, then you can (usually) ignore any harmful side effects the exception might have caused in code not written with exceptions in mind. This is hardly a desirable way to develop software, so you might as well accept the fact that using Standard C++ means understanding exceptions. Other articles [2,3,4] have addressed the problems that exceptions can cause, and I urge to you read at least one of these if you have not already done so.
My topic here is not handling exceptions, but throwing them. Throwing exceptions is seductively easy. This is one of the problems -- programmers are likely to start throwing exceptions with little thought to the consequences. I propose to present some guidelines for how and when to throw exceptions. A little extra care in the beginning can help get our exception handling off on the right foot.
THROWING EXCEPTIONS
Throwing an exception is where it all begins. A throw statement seems like a pretty trivial construct so not much discussion has been devoted to it. As usual in C++, things are not quite as simple as they seem. The very first thing to decide is: what do you use exceptions for?
Guideline 1 -- Throw an exception to indicate a failure of an operation.
There have been some tentative guidelines offered in this area. Some writers have stated that exceptions should be reserved for "truly exceptional conditions". In particular, exceptions should not be used as an alternate return path. Dr. Mueller [4] is even more specific. His rule is "Dont throw an exception unless you absolutely must." The problem with guidelines such as this is trying to determine what is, and what is not, a truly exceptional condition. Often, the context a function is used in determines whether the error is exceptional or not. Consider the simple MessageQueue class shown in listing 1.
The get_next() function throws an exception if the queue is empty. Is this a truly exceptional situation? It depends. In some cases, popping an empty queue might be considered a logic error severe enough to abort the program; in others it simply indicates that all information has been removed and processed. The developer of a class usually does not know in advance all the ways it will be used. Therefore, we must decide whether to throw an exception without knowing the context.
If we step back and look at exceptions from the outside, we note that the appearance of an exception indicates that the attempted operation aborted. If we do not handle the exception, it will also abort the function we are in, and all functions it propagates through, finally terminating the program itself if it is never handled. Information contained within the exception, including its type, may provide reasons for the failure, but the exception itself conveys the failure indication. From this view point, we arrive at Guideline #1. If a function detects an error that prevents it from succeeding, then it should throw an exception. In this sense, the exception from get_next() is valid.
Before I go on, consider the alternatives. The if in get_next() is quite typical of a large class of tests. If the test fails (that is, the if succeeds) then the function can not proceed. Before exceptions, these if statements either ended with a return; or an abort();. In fact, the latter is so common that it exists in the standard C library as the assert() macro. For function get_next(), an obvious alternative form is:
bool get_next(Message& msg);
The returned boolean indicates whether a valid new value was assigned to the Message reference parameter.
This is a very common idiom in C, but it has some problems (some would say that problems with this idiom are one of the primary motivations for using exceptions). In all fairness -- there is nothing fundamentally wrong with this idiom. It has worked for a lot of libraries and a lot of software. It does require some cooperation from users of the function to make sure the return value is tested; but it should be apparent from articles [2,3,4] that using exceptions does not relieve library clients from responsibility of cooperating in the error handling process. If anything, using exceptions correctly makes the clients participation mandatory. Nevertheless, indicating errors with a return value is problematic. How many of us can truthfully say that we have faithfully tested every return value we should have before using the returned object? Besides, all the necessary if statements clutter the code.
I have to mention another possibility. What happens if get_next() does no check at all and calls list<>::front() or list<>::pop_front() when the list is empty? It depends upon whose STL we are using. The public STL distribution from Hewlett-Packard has no error checking in these functions, which makes the test in get_next() critical. Some commercial STL libraries do provide error checks, though often they are not enabled by default. Just what the Standard C++ library version will do (when we have one) is something of an open issue (see the "STL and the Standard" sidebar). I prefer to have the test in get_next() in any case -- it usually never hurts to err on the safe side (for that purpose, exceptions are really very useful). Besides, my philosophy is that errors should be caught as early as possible.
One side effect of using exceptions to signal errors is that we must tighten up our specifications. A corollary of Guideline #1 is:
Corollary #1 -- Make sure the assumptions and conditions that your function requires are well defined and documented.
In other words, do not surprise your clients. If your specifications are clear, then an exception will be the expected result if an assumption is violated.
Guideline 2. - Throw exceptions derived from the standard exception classes.
Having decided that we are going the throw an exception, the next question is "what to throw?" The C++ Standard defines a class hierarchy of standard exceptions (table 1). Some of these are used by components of the language itself, and others within the standard library. In addition, there are classes in the hierarchy that are not used by any part of the standard. They are obviously intended to provide a starting point for other libraries. In all, the standard exception classes cover many typical errors. This means that clients of your libraries will be expecting exceptions to fall into the standard hierarchy. By using the standard hierarchy, you facilitate the development of exception handling in the clients code.
Table 1: Standard exception class hierarchy
exception
bad_alloc
bad_cast
bad_typeid
bad_exception
logic_error
domain_error
length_error
invalid_argument
out_of_range
runtime_error
range_error
overflow_error
In get_next() we threw a string object. This is pretty weak. When it comes to handling exceptions, the runtime mechanism differentiates by the exceptions type. Therefore we want to make the type of our exception (and not just its contents) indicative of the error we are reporting. The obvious standard exception that applies to an empty container is domain_error. It is a trivial change to write:
throw domain_error("Empty queue");
but it significantly improves our code.
Generally you do not want to throw a standard exception directly. Instead, derive your own exception class from the appropriate standard exception. This gives your clients a choice -- they can either catch all exceptions of a certain class (e.g. std::logic_error), or develop specific error handling code for a specific exception.
To avoid polluting the global namespace, exceptions of a class should either be nested within the class, or both the main class library and any exception classes should be defined within a namespace. In this case, we will assume that all the classes that are involved in handling messages are in a namespace MessageHandler. Our new version of get_next() throws an object of class MessageHandler::EmptyQueError, which is derived from domain_error. Since the type itself conveys all the useful information we have, class EmptyQueError is pretty simple.
namespace MessageHandler {
class EmptyQueError : public domain_error {
EmptyQueError() :
domain_error(const string("Empty queue")) {}
};
}
Guideline 3. - If you have multiple exception types, derive them all from a base class defined for that purpose.
This is not a contradiction of guideline #2, but an extension of it. By throwing an object of a class derived from one of the standard exception classes, you make it possible for clients to develop error handling code that can deal with the specific exception, or can deal with a broad class of standard errors in a uniform way. On the other hand, many clients will not care about the specific exception you throw, but only about catching any exception from your library. You can allow this by deriving all exception classes for your library from a single base class.
Combining Guidelines #2 and #3 is a legitimate use of multiple inheritance. In our example, there are probably several other Message handling exceptions besides the one in MessageQueue. We define a base class for them in the MessageHandler namespace.
namespace MessageHandler {
class Exception {
public:
};
Then we derive our version of EmptyQueError from Exception and domain_error.
namespace MessageHandler {
class EmptyQueError :
public domain_error,
public Exception
{
// ...
};
}
It is tempting to derive MessageHandler::Exception from std::exception. In the Standard iostream library, the exception base class failure is derived directly from std::exception. This means that iostream exceptions are not logic_errors or runtime_errors (see table 1), but simply iostream::failure(s). This approach might make sense for a large class library that defines numerous exceptions of its own. It might even make sense for our MessageHandler library, but in most cases we want to derive our individual exceptions from the appropriate subclass of std::exception. Since std::exception is not declared a virtual base class in its derived classes, having our own exception base class derive from std::exception would lead to multiple std::exception subobjects in the thrown exceptions. Therefore, my current feeling is that libraries should not derive their exception base classes from std::exception.
If it makes sense, a librarys exception classes can be organized into different sub-trees. As always, the goal is to give your clients as much flexibility as possible.
Guideline 4. - Try to provide useful information in the exception.
Another reason for deriving our own exception classes instead of just using the standard ones is that the standard exception classes are somewhat short on information content. The base class exception provides a virtual function what() which returns a const char*. Some exceptions provide a way to specify the string returned by what(), others simply return an "implementation defined NTBS* " (this includes the base class exception). Sometimes, a string is about all the information that we can supply, but in most cases we can do better.
In out Message Queue example, a Message is pretty simple:
class Message {
MsgHdr hdr;
string body;
public:
. . .
};
Suppose we encounter a message whose type is invalid for the operation requested. We have an exception for this: InvalidTypeError. When we throw an InvalidTypeError exception, we provide the type from MsgHdr and the operation that was attempted.
throw InvalidTypeError(hdr.type, operation);
We override function what() to include the additional information:
const char*
MessageHandler::InvalidTypeError::what()
{
static string str;
str.resize(0); // make sure the string is empty
ostringstream oss(str);
oss << "Invalid message type: "
<< type << " is not a valid message type for "
<< operation;
return str.c_str();
}
Guideline 5. - Give the client a way to avoid the exception, if possible.
We have decided that we are going to throw exceptions, and we have designed a set of exception classes that provide the client with adequate information about the errors when they happen, but we still have a problem. A truly general purpose library probably wants to provide mechanisms so that the client can avoid the exceptions if possible. The reason for this is explained back in Guideline #1 -- we do not know if the error we encounter is really exceptional for the client. Some clients of our MessageQueue may execute code in a loop that gets messages from the queue until it is empty. A get_next() on an empty queue generates an exception, but this is not an exceptional situation for those clients. As developers, we want to keep the normal code for our algorithms separated from the exception handling code. As class and library designers, we want to give our clients an alternative to writing normal code in exception handlers.
For MessageQueue this is simple: we just have to provide an extra function that allows the client to test whether the queue is empty.
bool empty() {return msgList.empty();}
Now the client can skip the call to get_next() when the queue is empty and avoid the exception. As noted above, currently the public STL takes this one step further and does NO checking -- the client is always responsible for checking the state of the container before calling front() and like functions.
Unfortunately, things are not always as simple as MessageQueue. In many cases, there is no easy way to tell in advance whether an operation will succeed or not -- the programmer must simply try the operation and see if it suceeded. In such cases, providing a means of avoiding an exception takes more work.
Guideline 6. - Give the client a way to disable the throwing of an exception.
There are several ways we can do this. One way is to let the client set a bit mask to disable undesired exceptions. The Standard iostream library uses something like this, only in reverse (in iostream, the client must set the exception mask to enable the exception). This is done for compatibility with existing code -- as a general rule, new code should throw exceptions by default and provide a means of disabling the exceptions if necessary.
An better approach is to create different versions of the function: one which throws an exception, and another which does not. These might be overloaded versions using the same name, or distinctly named versions. The Standard provides class nothrow in header <new> which is used to overload the Standard allocation functions operator new. Since nothrow is defined in the Standard, an overloaded function with an argument of this type would be a good way of providing this flexibility. For MessageQueue we might have:
Message get_next(); // throws EmptyQueueError
bool get_next(Message& msg); // returns false when queue empty
or perhaps
void get_next(Message& msg); // exception version
bool get_next(Message&, nothrow); // Guideline #6 version
Note that the point of Guideline #6 is to eliminate the exception, not to eliminate the error check. If the exception is disabled, some other way must be provided to indicate whether the operation failed. Any and all of the traditional error reporting techniques can be used, but it must be clear to the client what will happen if an exception is not thrown. For this reason, I prefer to use the multiple function approach rather than a bit mask. This makes it obvious from the useage that the function being called is not expected to throw an exception.
Guideline 7. - Give the client a way to disable unwanted runtime tests.
In Guideline #6 we are still doing error checking, we just do not throw an exception. For Guideline #7, we mean to eliminate the runtime error check altogether.
All exceptions result from the failure of some software check. These runtime checks fall into two broad categories: those that are an integral part of the algorithm being implemented, and all the others. The latter group includes all the tests that are added to code for the single purpose of detecting error conditions. In our get_next() function the test that throws the exception is an extra test. Obviously, this test is very important -- without it MessageQueue can enter an invalid state and return total garbage. Nevertheless, if we look at it from the clients point of view we see that it can be rather annoying to write code which tests MessagQueue::empty() only to then call a function that repeats the test. Such code may make our libraries more robust, but it does not always endear us to clients of those libraries (classes that override operator[] are often criticized for this type of induced inefficiency).
At this point I have to make it clear that this is Guideline #7 and not Guideline #2 or #3 because I do not think it is very important in the vast majority of cases. More often than not, such efficiency concerns either do not exist or do not outweigh the safety aspects of always having the tests enabled. Another aspect is often overlooked: these tests are often in inline functions. A decent compiler should be able to detect redundant tests in many cases and eliminate them automatically. Nevertheless, one of the primary goals of C++ is that it should be able to compete with C in terms of efficiency. Achieving C-like efficiency in a truly general purpose, reusable, library means giving the client who is willing to accept the risks some way of guaranteeing that the overhead of extra runtime tests is eliminated.
The most common way this is done is to eliminate the tests at compile time. As noted above, if the tests are in inline function calls, this might be left up to the compiler. If you do not trust the compiler, then a preprocessor symbol can be defined which removes the tests. This approach can even be used when the actual functionality is not provided via the inline function. The test goes in a public inline function where it can be compiled out (or automatically optimized away), and the actual work is done in a private function.
This technique works, and there are plenty of examples of commercial class libraries that use it. It suffers several drawbacks, however. First, it depends upon the preprocessor. The necessary symbols are effectively at global namespace. If several different libraries are being used within a translation unit, the possibility that two of them will use the same symbol increases. To avoid this, in practice the symbols will be fairly long and include some namespace information such as MESSAGE_QUEUE_NO_EXCEPTION. This works, but is clearly an aggravation.
Another drawback of this approach is that it typically is rather indiscriminate. It is possible to define NO_EXCEPTION for a single file, or even to place a matching pair of
#define NO_EXCEPTION
#undef NO_EXCEPTION
around some section of code, but in general the definition will go into the make file and apply to all the code in the application or library. This eliminates the possibility of selectively choosing efficiency over safety, or vise versa.
When removing tests at compile time is not viable, a couple of other options exist. One is a bit mask similar to that described in Guideline #6. In this case, the mask disables the test itself. The code still has to test the mask, but if the rest of the test is complex, there may still be a net gain in efficiency.
As with Guideline #6, the most promising technique is simply to provide multiple functions. One function performs tests and throws exceptions, another function does no tests and throws nothing. An example of this technique is in the Standard string class: the operator[] function does not provide any error checking, whereas another function (at()) provides the same capability as operator[], but at() checks its argument and will throw an out_of_range exception.
Applying both Guideline #6 and #7 to MessageQueue might end up looking something like this:
Message get_next(); // does check - throws exception
bool get_next(Message& msg); // does check - returns false
Message get_next(unchecked); // does no check - dies horribly
Class unchecked (used to overload the last version of get_next() above) is not provided by the Standard Library. While class nothrow could be used here, I prefer to use nothrow in Guideline #6 situations and have a different indicator that clearly shows that a function is unsafe. On the other hand, I do not like the idea of every library having to provide their own version of class unchecked, even with namespaces. Making unchecked a typedef synonym of nothrow will work in most cases. That way, everybody sticks with what is available in the Standard Library, but we get the differentiation that I desire.
Differentiating code this way clearly indicates the assumptions being made and the responsibilities being assumed. If a client wishes to use the safe version during development and then switch to the unsafe version for production, she can write the macro which will allow the compile time switch. That way, naming the preprocessor symbol is her problem.
Guideline 8. - Consider giving the client a way to replace the error reporting mechanism with a client defined version.
In the final analysis, a client might not want to handle your exceptions at all. Maybe their project does not want to use exceptions for efficiency reasons. Or maybe the client wants to integrate your library into another library or into a design that already has its own error reporting technique.
This can be accommodated by running all errors through a single error reporting function. This function in turn calls a client provided error handler. My preference would be for the error reporting function to throw exceptions by default, but call the client defined error handler function if one was provided. This technique is the familiar new_handler model. Something like the following shoul work:
void error( /* error arguments */ ) {
if (error_handler != nil) {
(*error_handler)( /* error arguments */ );
} else {
// throw exceptions
}
}
Obviously, the arguments passed to error_handler are going to be different for every library. Since we are going to replace the throw statements in our library with calls to error, my preference is to pass error the same object that would otherwise have been thrown. If Guideline #4 has been followed, then the object will contain the information needed by error(). If Guideline #3 is followed, then error(Exception& err) can be used for any exception that MessageHandler classes would throw.
You want to be careful about a couple of things with this technique.
A. It is tempting to write the default case in error(Exception& err) as:
throw err;
where err is the MessageHandler::Exception reference passed to error() above. This will not work as desired. Throwing an exception makes a copy of the object. Even though err is a reference to a polymorphic class, the copy constructor will slice out the base class portion. If the base class is an abstract class, this will not compile (which is good). We could use the RTTI mechanism to recover the original type of the object and throw it, but the C++ Way is to have our exception classes contain a virtual function which will simply turn around and throw themselves. This becomes:
err.throw_yourself();
B. If this technique is used, all functions which call error() must also provide a valid return statement after the call. Even though the default is to throw the exception, a client defined handler might return. If you wanted to insist that a client defined handler can not return (it must either throw the exception, throw another exception, or terminate the program), then put an abort(); statement in error().
This scheme may seem like a lot of unnecessary work. You may figure that exceptions are the standard way of signaling errors, and that clients are just going to have to cope with them. It may be true in many cases that this level of flexibility is not needed. Nevertheless, this type of flexibility can be very useful. With this capability in place, we can easily integrate one library into another. A specialized error_handler function for the first library can throw exceptions specific to the second library. This way, we can construct larger software modules with homogeneous error reporting properties by composing them from heterogeneous pieces.
One caveat has to be noted. This technique, and for that matter those of guidelines #5-#7 above are applicable only for those exceptions that are intended to go from within our library back to clients of that library. Internal functions which are written to catch and handle exceptions thrown by other library functions depend upon those exceptions being thrown. Therefore, such exceptions can not be disabled or changed. This may lead to cases where internal exceptions are caught and converted into external exceptions by top level routines. Certain aspects of this are discussed in my Coping With Exceptions article [4].
Guideline 9. - Throw a special exception to exit the program, if you must.
A couple of miscellaneous guidelines wrap things up. The idea of using an exception as a normal program exit mechanism may seem strange after all the discussion of error reporting. Nevertheless, like many things in C++, you sometimes find unexpected solutions to quirky programming problems in strange places. The problem is this: the only correct way to end a C++ program (according to the Standard -- so far), is to execute a return statement in the main() function. This destroys any local objects of main(), then proceeds to call exit(), which invokes the destructors for all global objects.
On the other hand, C programmers, and hence many C++ programmers, are use to ending their programs by calling exit() from wherever they happen to be when it becomes obvious that there is nothing more to do. Function exit() will invoke the destructors of all global objects, but it does not exit the current block. In other words, exit() does not unwind the stack. Even though the program is going away, not invoking destructors can leave dangling resources, which may, or may not, be cleaned-up by the operating system. In my humble opinion, if a program terminates normally, then it should exit cleanly. Function exit() does not unwind the stack, but it does clean-up global objects. An uncaught exception will unwind the stack, but if it propagates out of main() it will call terminate(), which calls abort(), which does not destroy global objects. A compromise (or should I say "combination") suggests itself. We can throw an exception to unwind the stack, and then call exit() from a catch block in main().
Since we want all local objects in main() to be destroyed before we enter the handler, this is a perfect place for a function-try-block. This gives us:
class program_exit {};
int
main(int argc, char* argv[])
try {
// . . .
}
catch (program_exit& ) {
return 0;
}
Note that program_exit is not derived from anything, not even std::exception. This is because program_exit is not a exception, which is to say, it is not an error indicator. Instead, program_exit is more like a specialized C++ version of LONGJUMP. Obviously, this technique only works if no intervening catch (...) clause handles the exception. This is Guideline #10 in [4] ("Always rethrow the exception in a catch (...) clause"). Hopefully, everybody is following that Guideline, at least. I do not recommend this as a general practice, nevertheless it is another technique that can go into the C++ programmers toolbox. It is better than depending upon the operating systems big broom to clean things up after a call to exit().
Guideline 10. - Do not throw exceptions from destructors.
Now we come to a case where we do not want to ever throw an exception if we can avoid it. We may not be able to avoid it, but let us take it as a goal that destructors should not throw exceptions. The problems with exceptions which emanate from a destructor, whether thrown by the destructor itself, or as a result of some function called by the destructor, were discussed at some length in [3] and [4]. I will not repeat the rational here, but it is important enough to reiterate the guideline.
CONCLUSIONS
These 10 guidelines establish a general framework for developing classes and libraries that use exceptions to indicate error conditions. Not all the guidelines will be applicable to every situation. Nevertheless, you should keep them in mind. The more flexible a class is designed to be, the easier it will be to reuse the class in other situations.
References
1. Working Paper for Draft Proposed International Standard for Information Systems -- Programming Language C++, January 1996.
2. Tom Cargill, "Exception handling: A false sense of security", C++ Report, Vol 6, No. 9, November-December 1994.
3. Harald M. Mueller, "10 Rules for Handling Exception Handling
Successfully," C++ Report, Vol. 8, No. 1, January 1996.
4. Jack Reeves, "Using Exceptions Effectively: Coping with Exceptions", C++ Report, Vol. 8, No. 2, March 1996.
Listing 1.
#include "Message.h"
#include <list>
class MessageQueue {
list<Message> msgList;
public:
void add(Message& msg) { msgList.push_back(msg); }
Message get_next();
void push_front(Message& msg) { msgList.push_front(msg); }
};
inline Message
MessageQueue::get_next()
{
if (msgList.empty())
throw string("Empty queue");
Message rtnMsg = msgList.front();
msgList.pop_front();
return rtnMsg;
}
Sidebar
STL and the Standard C++ Library
You will often read that the Standard Template Library (STL) has been adopted as part of the Standard C++ Library. It would be slightly more accurate to say that the STL has been adopted as the basis of several significant parts of the Standard C++ Library. The former statement is accurate enough for most conversations -- the Standard C++ Library includes all of the STL -- but there are a few additions defined in the Standard that are not included in the current public release of the STL. While minor in terms of functionality, these additions may be significant when examined from the standpoint of exceptions and error handling.
For example, the Standard defines a bitset container. While technically a bitset can be considered a specialization of vector<bool> (provided for efficiencies sake) it is documented as a separate container in Chapter 23. The description of bitset clearly indicates that certain member functions throw exceptions when appropriate.
The rest of Chapter 23 does not go into anywhere near the detailed description of the member functions of the other containers, i.e. the STL containers, as for bitset. The real documentation of the STL containers is in the tables, which are taken almost verbatim from the public STL specification. In the latest working paper, these tables now mention a couple of extra element access functions that appear in the headers of both the vector<> and the deque<> classes. These are:
reference at(size_type n);
const_reference at(size_type n) const;
These functions are equivalent to the operator[] functions, with the additional requirement that at() checks its argument and can throw an out_of_range exception if the value does not access a valid position within the container. This leaves such functions as front(), back(), pop_front(), and pop_back() in a gray area.
It would make sense for these functions also to throw an exception if they were invoked for an empty container. It appears from other portions of the document that the Standard Library intends to use exceptions in appropriate cases, but the current STL version of these functions do not throw exceptions and the current version of the Working Paper makes no mention of them doing so. Whether this will be changed, or whether additional "safe" functions might still be added to the STL containers of the Standard Library is an open question.
My own inclination would be to err on the side of safety, especially in the Standard Library. Besides, I want to see exceptions used in the Standard Library because the Standard Library provides a reference standard for how libraries should be designed. If the Standard Library uses exceptions, other libraries will use exceptions. If the Standard Library rejects error checking for commonly used idioms, or relegates it to secondary functions, then other libraries can be expected to do the same.
The Standard is still a draft, and on this issue we must wait and see what happens.