Chapter 3. General Concepts

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

Namespace std

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:

  1. 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;
  2. 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;
  3. 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.

Header Files

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.

Error and Exception Handling

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.

Standard Exception Classes

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:

Hierarchy of Standard Exceptions

Figure 3.1.. Hierarchy of Standard Exceptions

  1. Exceptions for language support

  2. Exceptions for the C++ standard library

  3. Exceptions for errors outside the scope of a program

Exception Classes for Language Support

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 Standard Library

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.

Exception Classes for Errors Outside the Scope of a Program

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.

Exceptions Thrown by the Standard Library

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.

Header Files for Exception Classes

The base class exception and class bad_exception are defined in <exception>. Class bad_alloc is defined in <new>. Classes bad_cast and bad_typeid are defined in <typeinfo>. Class ios_base::failure is defined in <ios>. All other classes are defined in <stdexcept>.

Members of 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.

Throwing Standard Exceptions

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)");

Deriving Standard Exception Classes

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.

Allocators

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.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.139.55.76