This chapter describes the fundamental concepts of the C++ standard library that you need to work with all or most components:
The namespace std
The names and formats of header files
The general concept of error and exception handling
A brief introduction to allocators
If you use different modules and/or libraries, you always have the potential for name clashes. This is because modules and libraries might use the same identifier for different things. This problem was solved by the introduction of namespaces into C++ (see Section 2.2.4, for an introduction to the concept of namespaces). A namespace is a certain scope for identifiers. Unlike a class, it is open for extensions that might occur at any source. Thus, you could use a namespace to define components that are distributed over several physical modules. A typical example of such a component is the C++ standard library, so it follows that it uses a namespace. In fact, all identifiers of the C++ standard library are defined in a namespace called std.
According to the concept of namespaces, you have three options when using an identifier of the C++ standard library:
You can qualify the identifier directly. For example, you can write std::ostream
instead of ostream.
A complete statement might look like this:
std::cout << std::hex << 3.4 << std::endl;
You can use a using declaration (see page 17). For example, the following code fragment introduces the local ability to skip std::
for cout
and endl.
using std::cout; using std::endl;
Thus the example in option 1 could be written like this:
cout << std::hex << 3.4 << endl;
You can use a using directive. (see page 17). This is the easiest option. By using a using directive for namespace std,
all identifiers of the namespace std
are available as if they had been declared globally. Thus, the statement
using namespace std;
allows you to write
cout << hex << 3.4 << endl;
Note that in complex code this might lead to accidental name clashes or, worse, to different behavior due to some obscure overloading rules. You should never use a using directive when the context is not clear (such as in header files, modules, or libraries).
The examples in this book are quite small, so for my own convenience, I usually use the last option throughout this book in complete example programs.
The use of namespace std
for all identifiers of the C++ standard library was introduced during the standardization process. This change is not backward compatible to old header files, in which identifiers of the C++ standard library are declared in the global scope. In addition, some interfaces of classes changed during the standardization process (however, the goal was to stay backward compatible if possible). So, a new style for the names of standard header files was introduced. This allows vendors to stay backward compatible by providing the old header files.
The definition of new names for the standard header files was a good opportunity to standardize the extensions of header files. Previously, several extensions for header files were used; for example, .h, .hpp,
and .hxx.
However, the new standard extension for header files might be a surprise: Standard headers no longer have extensions. Hence, include
statements for standard header files look like this:
#include <iostream> #include <string>
This also applies to header files assumed from the C standard. C header files now have the new prefix c
instead of the old extension .h:
#include <cstdlib> //was: <stdlib.h> #include <cstring> //was: <string.h>
Inside these header files, all identifiers are declared in namespace std.
One advantage of this naming scheme is that you can distinguish the old string header for char*
C functions from the new string header for the standard C++ class string:
#include <string> //C++ class string #include <cstring> //char* functions from C
Note that the new naming scheme of header files does not necessarily mean that the file names of standard header files have no extensions from the point of view of the operating system. How include
statements for standard header files are handled is implementation defined. C++ systems might add an extension or even use built-in declarations without reading a file. However, in practice, most systems simply include the header from a file that has exactly the same name that is used in the include
statement. So, in most systems, C++ standard header files simply have no extension. Note that this requirement for no extension applies only to standard header files. In general, it is still a good idea to use a certain extension for your own header files to help identify them in a file system.
To maintain compatibility with C, the "old" standard C header files are still available. So if necessary you can still use, for example,
#include <stdlib.h>
In this case, the identifiers are declared in both the global scope and in namespace std.
In fact, these headers behave as if they declare all identifiers in namespace std
followed by an explicit using declaration (see page 17).
For the C++ header files in the "old" format, such as <iostream.h>,
there is no specification in the standard (this changed more than once during the standardization process). Hence, they are not supported. In practice, most vendors will probably provide them to enable backward compatibility. Note that there were more changes in the headers than just the introduction of namespace std.
So in general you should either use the old names of header files or switch to the new standardized names.
The C++ standard library is heterogeneous. It contains software from very different sources that have different styles of design and implementation. Error and exception handling is a typical example of these differences. Parts of the library, such as string classes, support detailed error handling. They check for every possible problem that might occur and throw an exception if there is an error. Other parts, such as the STL (the standard template library) and valarrays, prefer speed over safety, so they rarely check for logical errors and throw exceptions only if runtime errors occur.
All exceptions thrown from the language or the library are derived from the base class exception.
This class is the root of several standard exception classes that form a hierarchy, as shown in Figure 3.1. These standard exception classes can be divided into three groups:
Exceptions for language support
Exceptions for the C++ standard library
Exceptions for errors outside the scope of a program
Exceptions for language support are used by language features. So in a way they are part of the core language rather than the library. These exceptions are thrown when the following operations fail.
An exception of class bad_alloc
is thrown whenever the global operator new
fails (except when the nothrow
version of new
is used). This is probably the most important exception because it might occur at any time in any nontrivial program.
An exception of class bad_cast
is thrown by the dynamic_cast
operator if a type conversion on a reference fails at runtime. The dynamic_cast
operator is described on page 19.
An exception of class bad_typeid
is thrown by the typeid
operator for runtime type identification. If the argument to typeid
is zero or the null pointer, this exception gets thrown.
An exception of class bad_exception
is used to handle unexpected exceptions. It does this by using the function unexpected(). unexpected()
is called if a function throws an exception that is not listed in an exception specification (exception specifications are introduced on page 16). For example:
class E1; class E2; // not derived from E1 void f() throw(E1) //throws only exceptions of type E1 { ... throw E1(); //throws exception of type E1 ... throw E2(); //calls unexpected(), which calls terminate() }
The throw of an exception of type E2
in f()
violates the exception specification. In this case, the function unexpected()
gets called, which usually calls terminate()
to terminate the program. However, if class bad_exception
is part of the exception specification, then unexpected()
usually rethrows an exception of this type:
class E1; class E2; // not derived from E1 void f() throw(E1, std::bad_exception) //throws exception of type E1 or //bad_exception for any other exception type { ... throw E1(); // throws exception of type E1 ... throw E2(); //calls unexpected(), which throws bad_exception }
Thus, if an exception specification includes the class bad_exception,
then any exception not part of the specification may be replaced by bad_exception
within the function unexpected().
[1]
Exception classes for the C++ standard library are usually derived from class logic_error.
Logic errors are errors that, at least in theory, could be avoided by the program; for example, by performing additional tests of function arguments. Examples of such errors are a violation of logical preconditions or a class invariant. The C++ standard library provides the following classes for logic errors:
An exception of class invalid_argument
is used to report invalid arguments, such as when a bitset (array of bits) is initialized with a char other than '0'
or '1'.
An exception of class length_error
is used to report an attempt to do something that exceeds a maximum allowable size, such as appending too many characters to a string.
An exception of class out_of_range
is used to report that an argument value is not in the expected range, such as when a wrong index is used in an array-like collection or string.
An exception of class domain_error
is used to report a domain error.
In addition, for the I/O part of the library, a special exception class called ios_base::failure
is provided. It may be thrown when a stream changes its state due to an error or end-of-file. The exact behavior of this exception class is described in Section 13.4.4.
Exceptions derived from runtime_error
are provided to report events that are beyond the scope of a program and are not easily avoidable. The C++ standard library provides the following classes for runtime errors:
An exception of class range_error
is used to report a range error in internal computations.
An exception of class overflow_error
is used to report an arithmetic overflow.
An exception of class underflow_error
is used to report an arithmetic underflow.
The C++ standard library itself can produce exceptions of classes range_error, out_of_range,
and invalid_argument.
However, because language features as well as user code are used by the library, their functions might throw any exception indirectly. In particular, bad_alloc
exceptions can be thrown whenever storage is allocated.
Any implementation of the standard library might offer additional exception classes (either as siblings or as derived classes). However, the use of these nonstandard classes makes code non-portable because you could not use another implementation of the standard library without breaking your code. So, you should always use only the standard exception classes.
To handle an exception in a catch
clause, you may use the exception interface. The interface of all standard exceptions classes contains only one member that can be used to get additional information besides the type itself: the member function what(),
which returns a null-terminated byte string:
namespace std { class exception { public: virtual const char* what() const throw(); ... }; }
The content of the string is implementation defined. It most likely (but not necessarily) determines the level of help and detail of such information. Note that the string might be a null-terminated multibyte string that is suitable to convert and display as wstring
(wstrings
are introduced in Section 11.2.1, page 480). The C-string, returned by what(),
is valid until the exception object from which it is obtained gets destroyed.[2]
The remaining members of the standard exception classes create, copy, assign, and destroy exception objects. Note that besides what()
there is no additional member for any of the standard exception classes that describes the kind of exception. For example, there is no portable way to find out the context of an exception or the faulty index of a range error. Thus, a portable evaluation of an exception could only print the message returned from what():
try {
...
}
catch (const std::exception& error) {
//print implementation-defined error message
std::cerr << error.what() << std::endl;
...
}
The only other possible evaluation might be an interpretation of the exact type of the exception. For example, due to a bad_alloc
exception, a program might try to get more memory.
You can throw standard exceptions inside your own library or program. All standard exception classes that enable you to do this have only one parameter to create the exception: a string
(class string
is described in Chapter 11) that will become the description returned by what().
For example, the class logic_error
is defined as follows:
namespace std { class logic_error : public exception { public: explicit logic_error (const string& whatString); }; }
The set of standard exceptions that provide this ability contains class logic_error
and its derived classes, class runtime_error
and its derived classes, as well as class ios_base::failure.
Thus, you can't throw exceptions of the base class exception
and any exception class that is provided for language support.
To throw a standard exception, you simply create a string that describes the exception and use it to initialize the thrown exception object:
std::string s; ... throw std::out_of_range(s);
Implicit conversions from char*
to string
exist, so you can also use a string literal directly:
throw std::out_of_range("out_of_range exception (somewhere, somehow)");
Another possibility for using the standard exception classes in your code is to define a special exception class derived directly or indirectly from class exception.
To do this, you must ensure that the what()
mechanism works.
The member function what()
is virtual. So, one way to provide what()
is to write your own implementation of what():
namespace MyLib { /* user-defined exception class * derived from a standard class for exceptions */ class MyProblem : public std::exception { public: ... MyProblem(...) { //special constructor } virtual const char* what() const throw() { //what() function ... } }; ... void f() { ... //create an exception object and throw it throw MyProblem(...); ... } }
Another way to provide the what()
function is to derive your exception class from one of the classes that have a string constructor for the what()
argument:
namespace MyLib { /* user-defined exception class * - derived from a standard class for exceptions * that has a constructor for the what() argument */ class MyRangeProblem : public std::out_of_range { public: MyRangeProblem (const string& whatString) : out_of_range(whatString) { } }; ... void f() { ... //create an exception object by using a string constructor and throw it throw MyRangeProblem("here is my special range problem"); ... } }
For examples that are part of a complete program, see class Stack
on page 441 and class Queue
on page 450.
The C++ standard library uses in several places special objects to handle the allocation and deallocation of memory. Such objects are called allocators. An allocator represents a special memory model. It is used as abstraction to translate the need to use memory into a raw call for memory. The use of different allocator objects at the same time allows you to use different memory models in a program.
Allocators originally were introduced as part of the STL to handle the nasty problem of different pointer types on PCs (such as near, far, and huge pointers). They now serve as a base for technical solutions that use certain memory models, such as shared memory, garbage collection, and object-oriented databases, without changing the interfaces. However, this use is relatively new and not yet widely adopted (this will probably change).
The C++ standard library defines a default allocator as follows:
namespace std { template <class T> class allocator; }
The default allocator is used as the default value everywhere an allocator can be used as an argument. It does the usual calls for memory allocation and deallocation; that is, it calls the new
and delete
operators. However, when or how often these operators are called is unspecified. Thus, an implementation of the default allocator might, for example, cache the allocated memory internally.
The default allocator is used in most programs. However, sometimes other libraries provide allocators to fit certain needs. In such cases you simply must pass them as arguments. Only occasionally does it make sense to program allocators. In practice, typically the default allocator is used. So the discussion of allocators is deferred until Chapter 15, which covers in detail not only allocators, but also their interfaces.
[1] You can modify the exact behavior of unexpected().
However, a function never throws exceptions other than those stated in its exception specification (if any).
[2] The specification of the lifetime of the return value of what()
is not specified in the original standard. However, this is the proposed resolution to fix this problem.
3.148.107.254