Chapter 4. Utilities

This chapter describes the general utilities of the C++ standard library. These utilities are:

  • Small, simple classes and functions that perform often-needed tasks

  • Several general types

  • Some important C functions

  • Numeric limits[1]

Most, but not all, of these utilities are described in clause 20, "General Utilities," of the C++ Standard, and their definitions can be found in the <utility> header. The rest are described along with more major components of the library either because they are used primarily with that particular component or due to historical reasons. For example, some general auxiliary functions are defined as part of the <algorithm> header, although they are not algorithms in the sense of the STL (which is described in Chapter 5).

Several of these utilities are also used within the C++ standard library. In particular, the type pair is used whenever two values need to be treated as single unit (for example, if a function has to return two values).

Pairs

The class pair is provided to treat two values as a single unit. It is used in several places within the C++ standard library. In particular, the container classes map and multimap use pairs to manage their elements, which are key/value pairs (See Section 6.6 page 194). Another example of the usage of pairs is functions that return two values.

The structure pair is defined in <utility> as follows:

   namespace std {
       template <class T1, class T2>
       struct pair {
           //type names for the values
           typedef T1 first_type;
           typedef T2 second_type;


           //member
           T1 first;
           T2 second;


           /* default constructor
            * - T1 () and T2 () force initialization for built-in types
            */
           pair()
            : first(T1()), second(T2()) {
           }


           //constructor for two values
           pair (const T1& a, const T2& b)
            : first(a), second(b) {
           }


           //copy constructor with implicit conversions
           template<class U, class V>
           pair (const pair<U,V>& p)
            : first(p.first), second(p.second) {
           }
       };


       //comparisons
       template <class T1, class T2>
       bool operator== (const pair<T1,T2>&, const pair<T1,T2>&);
       template <class T1, class T2>
       bool operator< (const pair<T1,T2>&, const pair<T1,T2>&);
       ... //similar: !=, <=, >, >=
//convenience function to create a pair
       template <class T1, class T2>
       pair<T1,T2> make_pair (const T1&, const T2&);
   }

Note that the type is declared as struct instead of class so that all members are public. Thus, for any value pair, direct access to the individual values is possible.

The default constructor creates a value pair with values that are initialized by the default constructor of their type. Because of language rules, an explicit call of a default constructor also initializes fundamental data types such as int. Thus, the declaration

   std::pair<int,float> p;    // initialize p.first and p.second with zero

initializes the values of p by using int() and float(), which yield zero in both cases. See page 14 for a description of the rules for explicit initialization for fundamental types.

The template version of a copy constructor provided here is used when implicit type conversions are necessary. If an object of type pair gets copied, the normal implicitly generated default copy constructor is called.[2] For example:

   void f(std::pair<int,const char*>);
   void g(std::pair<const int,std::string>);
   ...
   void foo {
       std::pair<int,const char*> p(42,"hello");
       f(p);    //OK: calls built-in default copy constructor
       g(p);    //OK: calls template constructor
   }

Pair Comparisons

For the comparison of two pairs, the C++ standard library provides the usual comparison operators. Two value pairs are equal if both values are equal:

   namespace std {
       template <class T1, class T2>
       bool operator== (const pair<T1,T2>& x, const pair<T1,T2>& y) {
           return x.first == y.first && x.second == y.second;
       }
   }

In a comparison of pairs, the first value has higher priority. Thus, if the first values of two pairs differ, the result of their comparison is used as the result of the comparison of the whole pairs. If the first values are equal, the comparison of the second values yields the result:

    namespace std {
       template <class T1, class T2>
       bool operator< (const pair<T1,T2>& x, const pair<T1,T2>& y) {
           return x.first < y.first ||
                  (!(y.first < x.first) && x.second < y.second);
       }
    }

The other comparison operators are defined accordingly.

Convenience Function make_pair()

The make_pair() template function enables you to create a value pair without writing the types explicitly[3]:

    namespace std {
        //create value pair only by providing the values
        template <class T1, class T2>
        pair<T1,T2> make_pair (const T1& x, const T2& y) {
            return pair<T1,T2>(x, y);
        }
    }

For example, by using make_pair() you can write

    std::make_pair(42, '@')

instead of

    std::pair<int,char>(42,'@')

In particular, the make_pair() function makes it convenient to pass two values of a pair directly to a function that requires a pair as its argument. Consider the following example:

    void f(std::pair<int,const char*>);
    void g(std::pair<const int,std::string>);
    ...
    void foo {
        f(std::make_pair(42,"hello"));     //pass two values as pair
        g(std::make_pair(42,"hello"));     //pass two values as pair
// with type conversions
    }

As the example shows, make_pair() makes it rather easy to pass two values as one pair argument. It works even when the types do not match exactly because the template constructor provides implicit type conversion. When you program by using maps or multimaps, you often need this ability (see page 203).

Note that an expression that has the explicit type description has an advantage because the resulting type of the pair is clearly defined. For example, the expression

    std::pair<int,float>(42,7.77)

does not yield the same as

    std::make_pair(42,7.77)

The latter creates a pair that has double as the type for the second value (unqualified floating literals have type double). The exact type may be important when overloaded functions or templates are used. These functions or templates might, for example, provide versions for both float and double to improve efficiency.

Examples of Pair Usage

The C++ standard library uses pairs a lot. For example, the map and multimap containers use pair as a type to manage their elements, which are key/value pairs. See Section 6.6, for a general description of maps and multimaps, and in particular page 91 for an example that shows the usage of type pair. Objects of type pair are also used inside the C++ standard library in functions that return two values (see page 183 for an example).

Class auto_ptr

This section covers the auto_ptr type. The auto_ptr type is provided by the C++ standard library as a kind of a smart pointer that helps to avoid resource leaks when exceptions are thrown. Note that I wrote "a kind of a smart pointer." There are several useful smart pointer types. This class is smart with respect to only one certain kind of problem. For other kinds of problems, type auto_ptr does not help. So, be careful and read the following subsections.

Motivation of Class auto_ptr

Functions often operate in the following way[4]:

  1. Acquire some resources.

  2. Perform some operations.

  3. Free the acquired resources.

If the resources acquired on entry are bound to local objects, they get freed automatically on function exit because the destructors of those local objects are called. But if resources are acquired explicitly and are not bound to any object, they must be freed explicitly. Resources are typically managed explicitly when pointers are used.

A typical example of using pointers in this way is the use of new and delete to create and destroy an object:

    void f()
    {
        ClassA* ptr = new ClassA;     //create an object explicitly
        ...                            //perform some operations
        delete ptr;                    //clean up (destroy the object explicitly)
    }

This function is a source of trouble. One obvious problem is that the deletion of the object might be forgotten (especially if you have return statements inside the function). There also is a not-so-obvious danger that an exception might occur. Such an exception would exit the function immediately without calling the delete statement at the end of the function. The result would be a memory leak or, more generally, a resource leak. Avoiding such a resource leak usually requires that a function catches all exceptions. For example:

   void f()
   {
       ClassA* ptr = new ClassA;     //create an object explicitly


       try {
          ...                        //perform some operations
       }
       catch (...) {                  //for any exception
           delete ptr;                //- clean up
           throw;                     //- rethrow the exception
       }


       delete ptr;                    //clean up on normal end
   }

To handle the deletion of this object properly in the event of an exception, the code gets more complicated and redundant. If a second object is handled in this way, or if more than one catch clause is used, the problem gets worse. This is bad programming style and should be avoided because it is complex and error prone.

A kind of smart pointer can help here. The smart pointer can free the data to which it points whenever the pointer itself gets destroyed. Furthermore, because the pointer is a local variable, it gets destroyed automatically when the function is exited regardless of whether the exit is normal or is due to an exception. The class auto_ptr was designed to be such a kind of smart pointer.

An auto_ptr is a pointer that serves as owner of the object to which it refers (if any). As a result, an object gets destroyed automatically when its auto_ptr gets destroyed. A requirement of an auto_ptr is that its object has only one owner.

Here is the previous example rewritten to use an auto_ptr:

//header file for auto_ptr
    #include <memory>


    void f()
    {
        //create and initialize an auto_ptr
        std::auto_ptr<ClassA> ptr(new ClassA);


        ...                           //perform some operations
    }

The delete statement and the catch clause are no longer necessary. An auto_ptr has much the same interface as an ordinary pointer; that is, operator * dereferences the object to which it points, whereas operator -> provides access to a member if the object is a class or a structure. However, any pointer arithmetic (such as ++) is not defined (this might be an advantage, because pointer arithmetic is a source of trouble).

Note that class auto_ptr<> does not allow you to initialize an object with an ordinary pointer by using the assignment syntax. Thus, you must initialize the auto_ptr directly by using its value[5]:

   std::auto_ptr<ClassA> ptr1(new ClassA);     //OK
   std::auto_ptr<ClassA> ptr2 = new ClassA;    //ERROR

Transfer of Ownership by auto_ptr

An auto_ptr provides the semantics of strict ownership. This means that because an auto_ptr deletes the object to which it points, the object should not be "owned" by any other objects. Two or more auto_ptrs must not own the same object at the same time. Unfortunately, it might happen that two auto_ptrs own the same object (for example, if you initialize two auto_ptrs with the same object). Making sure this doesn't happen is up to the programmer.

This leads to the question of how the copy constructor and the assignment operator of auto_ptrs operate. The usual behavior of these operations would be to copy the data of one auto_ptr to the other. However, this behavior would result in the situation, in which two auto_ptrs own the same object. The solution is simple, but it has important consequences: The copy constructor and assignment operator of auto_ptrs "transfer ownership" of the objects to which they refer.

Consider, for example, the following use of the copy constructor:

//initialize an auto_ptr with a new object
    std::auto_ptr<ClassA> ptr1(new ClassA);


    //copy the auto_ptr
    //- transfers ownership from ptr1 to ptr2
    std::auto_ptr<ClassA> ptr2(ptr1);

After the first statement, ptr1 owns the object that was created with the new operator. The second statement transfers ownership from ptr1 to ptr2. So after the second statement, ptr2 owns the object created with new, and ptr1 no longer owns the object. The object created by new ClassA gets deleted exactly once — when ptr2 gets destroyed.

The assignment operator behaves similarly:

//initialize an auto_ptr with a new object
    std::auto_ptr<ClassA> ptr1(new ClassA);
    std::auto_ptr<ClassA> ptr2;  //create another auto_ptr


    ptr2 = ptr1;                 //assign the auto_ptr
                                 //- transfers ownership from ptr1 to ptr2

Here, the assignment transfers ownership from ptr1 to ptr2. As a result, ptr2 owns the object that was previously owned by ptr1.

If ptr2 owned an object before an assignment, delete is called for that object:

//initialize an auto_ptr with a new object
    std::auto_ptr<ClassA> ptr1(new ClassA);
    //initialize another auto_ptr with a new object
    std::auto_ptr<ClassA> ptr2(new ClassA);


    ptr2 = ptr1;                  //assign the auto_ptr
                                  //- delete object owned by ptr2
                                  //- transfers ownership from ptr1 to ptr2

Note that a transfer of ownership means that the value is not simply copied. In all cases of ownership transfer, the previous owner (ptr1 in the previous examples) loses its ownership. As a consequence the previous owner has the null pointer as its value after the transfer. This is a significant violation of the general behavior of initializations and assignments in programming languages. Here, the copy constructor modifies the object that is used to initialize the new object, and the assignment operator modifies the right-hand side of the assignment. It is up to the programmer to ensure that an auto_ptr that lost ownership and got the null pointer as value is no longer dereferenced.

To assign a new value to an auto_ptr, this new value must be an auto_ptr. You can't assign an ordinary pointer:

    std::auto_ptr<ClassA> ptr;                         //create an auto_ptr


    ptr = new ClassA;                                  //ERROR 
    ptr = std::auto_ptr<ClassA>(new ClassA);           //OK, delete old object
//    and own new

Source and Sink

The transfer of ownership implies a special use for auto_ptrs; that is, functions can use them to transfer ownership to other functions. This can occur in two different ways:

  1. A function can behave as a sink of data. This happens if an auto_ptr is passed as an argument to the function by value. In this case, the parameter of the called function gets ownership of the auto_ptr. Thus, if the function does not transfer it again, the object gets deleted on function exit:

       void sink(std::auto_ptr<ClassA>);       //sink() gets ownership
  2. A function can behave as a source of data. When an auto_ptr is returned, ownership of the returned value gets transferred to the calling function. The following example shows this technique:

       std::auto_ptr<ClassA> f()
       {
           std::auto_ptr<ClassA> ptr(new ClassA); //ptr owns the new object
           ...
           return ptr;         //transfer ownership to calling function
       }
    
    
       void g()
       {
           std::auto_ptr<ClassA> p;
    
    
           for (int i=0; i<10; ++i) {
               p = f();        //p gets ownership of the returned object
                               //(previously returned object of f() gets deleted)
               ...
           }
       }                       //last-owned object of p gets deleted

    Each time f() is called, it creates an object with new and returns the object, along with its ownership, to the caller. The assignment of the return value to p transfers ownership to p. In the second and additional passes through the loop, the assignment to p deletes the object that p owned previously. Leaving g(), and thus destroying p, results in the destruction of the last object owned by p. In any case, no resource leak is possible. Even if an exception is thrown, any auto_ptr that owns data ensures that this data is deleted.

Caveat

The semantics of auto_ptr always include ownership, so don't use auto_ptrs in a parameter list or as a return value if you don't mean to transfer ownership. Consider, for example, the following naive implementation of a function that prints the object to which an auto_ptr refers. Using it would be a disaster.

//this is a
bad example
   template <class T>
   void bad_print(std::auto_ptr<T> p)    //p gets ownership of passed argument
   {
      //does p own an object ?
      if (p.get() == NULL) {
             std::cout << "NULL";
      }
      else {
          std::cout << *p;
      }
   }            //Oops, exiting deletes the object to which p refers

Whenever an auto_ptr is passed to this implementation of bad_print(), the objects it owns (if any) are deleted. This is because the ownership of the auto_ptr that is passed as an argument is passed to the parameter p, and p deletes the object it owns on function exit. This is probably not the programmer's intention and would result in fatal runtime errors:

   std::auto_ptr<int> p(new int);
   *p = 42;         //change value to which p refers
   bad_print(p);   //Oops, deletes the memory to which p refers
   *p = 18;         //RUNTIME ERROR

You might think about passing auto_ptrs by reference instead. However, passing auto_ptrs by reference confuses the concept of ownership. A function that gets an auto_ptr by reference might or might not transfer ownership. Allowing an auto_ptr to pass by reference is very bad design and you should always avoid it.

According to the concept of auto_ptrs, it is possible to transfer ownership into a function by using a constant reference. This is very dangerous because people usually expect that an object won't get modified when you pass it as a constant reference. Fortunately, there was a late design decision that made auto_ptrs less dangerous. By some tricky implementation techniques, transfer of ownership is not possible with constant references. In fact, you can't change the ownership of any constant auto_ptr:

   const std::auto_ptr<int> p(new int);
   *p = 42;         //change value to which p refers
   bad_print(p);    //COMPILE-TIME ERROR
   *p = 18;         //OK

This solution makes auto_ptrs safer than they were before. Many interfaces use constant references to get values that they copy internally. In fact, all container classes (see Chapter 6 or Chapter 10 for examples) of the C++ standard library behave this way, which might look like the following:

   template <class T>
   void container::insert (const T& value)
   {
         ...
         X = value;   //assign or copy value internally
         ...
   }

If such an assignment was possible for auto_ptrs, the assignment would transfer ownership into the container. However, because of the actual design of auto_ptrs, this call results in an error at compile time:

container<std::auto_ptr<int> > c;
   const std::auto_ptr<int> p(new int);
   ...
   c.insert(p);     //ERROR
   ...

All in all, constant auto_ptrs reduce the danger of an unintended transfer of ownership. Whenever an object is passed via an auto_ptr, you can use a constant auto_ptr to signal the end of the chain.

The const does not mean that you can't change the value of the object the auto_ptr owns (if any). You can't change the ownership of a constant auto_ptr; however, you can change the value of the object to which it refers. For example:

    std::auto_ptr<int> f()
    {
       const std::auto_ptr<int> p(new int);   //no ownership transfer possible
       std::auto_ptr<int> q(new int);         //ownership transfer possible


       *p = 42;          //OK, change value to which p refers
       bad_print(p);     //COMPILE-TIME ERROR
       *p = *q;          //OK, change value to which p refers
       p = q;            //COMPILE-TIME ERROR
       return p;         //COMPILE-TIME ERROR
    }

Whenever the const auto_ptr is passed or returned as an argument, any attempt to assign a new object results in a compile-time error. With respect to the constness, a const auto_ptr behaves like a constant pointer (T* const p) and not like a pointer that refers to a constant (const T* p); although the syntax looks the other way around.

auto_ptrs as Members

By using auto_ptrs within a class you can also avoid resource leaks. If you use an auto_ptr instead of an ordinary pointer, you no longer need a destructor because the object gets deleted with the deletion of the member. In addition, an auto_ptr helps to avoid resource leaks that are caused by exceptions that are thrown during the initialization of an object. Note that destructors are called only if any construction is completed. So, if an exception occurs inside a constructor, destructors are only called for objects that have been fully constructed. This might result in a resource leak if, for example, the first new was successful but the second was not. For example:

   class ClassB {
     private:
       ClassA* ptr1;                 //pointer members
       ClassA* ptr2;
     public:
       //constructor that initializes the pointers
//- will cause resource leak if second new throws
       ClassB (ClassA val1, ClassA val2)
        : ptr1(new ClassA(val1)), ptr2(new ClassA(val2)) {
       }


       //copy constructor
//- might cause resource leak if second new throws
       ClassB (const ClassB& x)
        : ptr1(new ClassA(*x.ptr1)), ptr2(new ClassA(*x.ptr2)) {
       }


       //assignment operator
       const ClassB& operator= (const ClassB& x) {
          *ptr1 = *x.ptr1;
          *ptr2 = *x.ptr2;
          return *this;
       }


       ~ClassB () {
           delete ptr1;
           delete ptr2;
       }
       ...
   };

To avoid such a possible resource leak, you can simply use auto_ptrs:

   class ClassB {
     private:
       const std::auto_ptr<ClassA> ptr1;         //auto_ptr members
       const std::auto_ptr<ClassA> ptr2;
     public:
       //constructor that initializes the auto_ptrs
//- no resource leak possible
       ClassB (ClassA val1, ClassA val2)
        : ptr1(new ClassA(val1)), ptr2(new ClassA(val2)) {
       }


       //copy constructor
//- no resource leak possible
       ClassB (const ClassB& x)
        : ptr1(new ClassA(*x.ptr1)), ptr2(new ClassA(*x.ptr2)) {
       }


       //assignment operator
       const ClassB& operator= (const ClassB& x) {
          *ptr1 = *x.ptr1;
          *ptr2 = *x.ptr2;
          return *this;
       }


       //no destructor necessary
//(default destructor lets ptr1 and ptr2 delete their objects)
       ...
   };

Note, however, that although you can skip the destructor, you still have to program the copy constructor and the assignment operator. By default, both would try to transfer ownership, which is probably not the intention. In addition, and as mentioned on page 42, to avoid an unintended transfer of ownership you should use constant auto_ptrs here if the auto_ptr should not refer to the same object throughout its lifetime.

Misusing auto_ptrs

auto_ptrs satisfy a certain need; namely, to avoid resource leaks when exception handling is used. Unfortunately, the exact behavior of auto_ptrs changed in the past and no other kind of smart pointers are provided in the C++ standard library, so people tend to misuse auto_ptrs. Here are some hints to help you use them correctly:

  1. auto_ptrs cannot share ownership.

    An auto_ptr must not refer to an object that is owned by another auto_ptr (or other object). Otherwise, if the first pointer deletes the object, the other pointer suddenly refers to a destroyed object, and any further read or write access may result in disaster.

  2. auto_ptrs are not provided for arrays.

    An auto_ptr is not allowed to refer to arrays. This is because an auto_ptr calls delete instead of delete[] for the object it owns. Note that there is no equivalent class in the C++ standard library that has the auto_ptr semantics for arrays. Instead, the library provides several container classes to handle collections of data (see Chapter 5).

  3. auto_ptrs are not "universal smart pointers."

    An auto_ptr is not designed to solve other problems for which smart pointers might be useful. In particular, they are not pointers for reference counting. (Pointers for reference counting ensure that an object gets deleted only if the last of several smart pointers that refer to that object gets destroyed.)

  4. auto_ptrs don't meet the requirements for container elements.

    An auto_ptr does not meet one of the most fundamental requirements for elements of standard containers. That is, after a copy or an assignment of an auto_ptr, source and sink are not equivalent. In fact, when an auto_ptr is assigned or copied, the source auto_ptr gets modified because it transfers its value rather than copying it. So you should not use an auto_ptr as an element of a standard container. Fortunately, the design of the language and library prevents this misuse from compiling in a standard-conforming environment.

Unfortunately, sometimes the misuse of an auto_ptr works. Regarding this, using nonconstant auto_ptrs is no safer than using ordinary pointers. You might call it luck if the misuse doesn't result in a crash, but in fact you are unlucky because you don't realize that you made a mistake.

See Section 5.10.2, for a discussion and Section 6.8, for an implementation of a smart pointer for reference counting. This pointer is useful when sharing elements in different containers.

auto_ptr Examples

The first example shows how auto_ptrs behave regarding the transfer of ownership:

//util/autoptr1.cpp

   #include <iostream>
   #include <memory>
   using namespace std;


   /* define output operator for auto_ptr
    * - print object value or NULL
*/
   template <class T>
   ostream& operator<< (ostream& strm, const auto_ptr<T>& p)
   {
       //does p own an object ?
       if (p.get() == NULL) {
           strm << "NULL";        //NO: print NULL
        }
        else {
           strm << *p;           //YES: print the object
        }
        return strm;
   }


   int main()
   {
       auto_ptr<int> p(new int(42));
       auto_ptr<int> q;


       cout << "after initialization:" << endl;
       cout << " p: " << p << endl;
       cout << " q: " << q << endl;


       q = p;
       cout << "after assigning auto pointers:" << endl;
       cout << " p: " << p << endl;
       cout << " q: " << q << endl;


       *q += 13;                   //change value of the object q owns
       p = q;
       cout << "after change and reassignment:" << endl;
       cout << " p: " << p << endl;
       cout << " q: " << q << endl;
   }

The output of the program is as follows:

   after initialization:
    p: 42
    q: NULL
   after assigning auto pointers:
    p: NULL
    q: 42
   after change and reassignment:
    p: 55
    q: NULL

Note that the second parameter of the output operator function is a constant reference. So it uses auto_ptrs without any transfer of ownership.

As mentioned on page 40, bear in mind that you can't initialize an auto_ptr by using the assignment syntax or assign an ordinary pointer:

    std::auto_ptr<int> p(new int(42));     //OK
    std::auto_ptr<int> p = new int(42);    //ERROR


    p = std::auto_ptr<int>(new int(42));   //OK
    p = new int(42);                       //ERROR

This is because the constructor to create an auto_ptr from an ordinary pointer is declared as explicit (see Section 2.2.6, for an introduction of explicit).

The following example shows how constant auto_ptrs behave:

//util/autoptr2.cpp

   #include <iostream>
   #include <memory>
   using namespace std;


   /* define output operator for auto_ptr
    * - print object value or NULL
*/
   template <class T>
   ostream& operator<< (ostream& strm, const auto_ptr<T>& p)
   {
       //does p own an object ?
       if (p.get() == NULL) {
           strm << "NULL";       //NO: print NULL
       }
       else {
           strm << *p;           //YES: print the object
       }
       return strm;
   }


   int main()
   {
       const auto_ptr<int> p(new int(42));
       const auto_ptr<int> q(new int(0));
       const auto_ptr<int> r;
       cout << "after initialization:" << endl;
       cout << " p: " << p << endl;
       cout << " q: " << q << endl;
       cout << " r: " << r << endl;


       *q = *p;
   //  *r = *p;    //ERROR: undefined behavior
       *p = −77;
       cout << "after assigning values:" << endl;
       cout << " p: " << p << endl;
       cout << " q: " << q << endl;
       cout << " r: " << r << endl;


   //  q = p;      //ERROR at compile time
   //  r = p;      //ERROR at compile time
   }

Here, the output of the program is as follows:

   after initialization:
    p: 42
    q: 0
    r: NULL
   after assigning values:
    p: −77
    q: 42
    r: NULL

This example defines an output operator for auto_ptrs. To do this, it passes an auto_ptr as a constant reference. According to the discussion on page 43, you should usually not pass an auto_ptr in any form. This function is an exception to this rule.

Note that the assignment

   *r = *p;

is an error. It dereferences an auto_ptr that refers to no object. According to the standard, this results in undefined behavior; for example, a crash. As you can see, you can manipulate the objects to which constant auto_ptrs refer, but you can't change which objects they own. Even if r was nonconstant, the last statement would not be possible because it would change the constant p.

Class auto_ptr in Detail

Class auto_ptr is declared in <memory>:

   #include <memory>

It provides auto_ptr as a template class for any types in namespace std. The following is the exact declaration of the class auto_ptr:[6]

   namespace std {
       //auxiliary type to enable copies and assignments
       template <class Y> struct auto_ptr_ref {};


       template<class T>
       class auto_ptr {
         public:
           //type names for the value
           typedef T element_type;


           //constructor
           explicit auto_ptr(T* ptr = 0) throw();


           //copy constructors (with implicit conversion)
//- note: nonconstant parameter
           auto_ptr(auto_ptr&) throw();
           template<class U> auto_ptr(auto_ptr<U>&) throw();


           //assignments (with implicit conversion)
//- note: nonconstant parameter
           auto_ptr& operator= (auto_ptr&) throw();
           template<class U>
               auto_ptr& operator= (auto_ptr<U>&) throw();


           //destructor 
           ~auto_ptr() throw();


           //value access
           T* get() const throw();
           T& operator*() const throw();
           T* operator->() const throw();


           //release ownership
           T* release() throw();


           //reset value
           void reset(T* ptr = 0) throw();


         //special conversions to enable copies and assignments
         public:
           auto_ptr(auto_ptr_ref<T>) throw();
           auto_ptr& operator= (auto_ptr_ref<T> rhs) throw();
           template<class U> operator auto_ptr_ref<U>() throw();
           template<class U> operator auto_ptr<U>() throw();
      };
   }

The individual members are described in detail in the following sections, in which auto_ptr is an abbreviation for auto_ptr<T>. A complete sample implementation of class auto_ptr is located on page 56.

Type Definitions

auto_ptr:: element_type

  • The type of the object that the auto_ptr owns.

Constructors, Assignments, and Destructors

auto_ptr::auto_ptr () throw()

  • The default constructor.

  • Creates an auto_ptr that does not own an object.

  • Initializes the value of the auto_ptr with zero.

explicit auto_ptr::auto_ptr (T* ptr) throw()

  • Creates an auto_ptr that owns and points to the object to which ptr refers.

  • After the call, *this is the owner of the object to which ptr refers. There must be no other owner.

  • If ptr is not the null pointer, it must be a value returned by new because the destructor of the auto_ptr calls delete automatically for the object it owns.

  • It is not correct to pass the return value of a new array that was created by new []. (For arrays, the STL container classes, which are introduced in Section 5.2, Page 75, should be used.)

auto_ptr::auto_ptr (auto_ptr& ap) throw()

template<class U> auto_ptr::auto_ptr (auto_ptr<U>& ap) throw()

  • The copy constructor (for nonconstant values).

  • Creates an auto_ptr that adopts the ownership of the object ap owned on entry. The ownership of an object to which ap referred on entry (if any) is transferred to *this.

  • After the operation, ap no longer owns an object. Its value becomes the null pointer. Thus, in contrast to the usual implementation of a copy constructor, the source object gets modified.

  • Note that this function is overloaded with a member template (see page 11 for an introduction to member templates). This enables automatic type conversions from the type of ap to the type of the created auto_ptr; for example, to convert an auto_ptr to an object of a derived class into an auto_ptr to an object of a base class.

  • See Section 4.2.2, Page 40, for a discussion of the transfer of ownership.

auto_ptr& auto_ptr::operator = (auto_ptr& ap) throw()

template<class U> auto_ptr& auto_ptr::operator = (auto_ptr<U>& ap) throw()

  • The assignment operator (for nonconstant values).

  • Deletes the object it owns on entry (if any) and adopts the ownership of the object that ap owned on entry. Thus, the ownership of an object to which ap referred on entry (if any) is transferred to *this.

  • After the operation, ap no longer owns an object. Its value becomes the null pointer. Thus, in contrast to the usual implementation of an assignment operator, the source object gets modified.

  • The object to which the auto_ptr on the left-hand side of the assignment (*this) refers is deleted by calling delete for it.

  • Note that this function is overloaded with a member template (see page 11 for an introduction to member templates). This enables automatic type conversions from the type of ap to the type of *this; for example, to convert an auto_ptr to an object of a derived class into an auto_ptr to an object of a base class.

  • See Section 4.2.2, Page 40, for a discussion about the transfer of ownership.

auto_ptr::~auto_ptr () throw()

  • The destructor.

  • If the auto_ptr owns an object on entry, it calls delete for it.

Value Access

T* auto_ptr::get () const throw()

  • Returns the address of the object that the auto_ptr owns (if any).

  • Returns the null pointer if the auto_ptr does not own an object.

  • This call does not change the ownership. Thus, on exit the auto_ptr still owns the object that it owned on entry (if any).

T& auto_ptr::operator * () const throw()

  • The dereferencing operator.

  • Returns the object that the auto_ptr owns.

  • If the auto_ptr does not own an object, the call results in undefined behavior (which may result in a crash).

T* auto_ptr::operator-> () const throw()

  • The operator for member access.

  • Returns a member of the object that the auto_ptr owns.

  • If the auto_ptr does not own an object, the call results in undefined behavior (which may result in a crash).

Value Manipulation

T* auto_ptr::release () throw()

  • Releases the ownership of the object that the auto_ptr owns.

  • Returns the address of the object that the auto_ptr owned on entry (if any).

  • Returns the null pointer if the auto_ptr does not own an object on entry.

void auto_ptr::reset (T* ptr = 0) throw()

  • Reinitializes the auto_ptr with ptr.

  • deletes the object that the auto_ptr owns on entry (if any).

  • After the call, *this is the owner of the object to which ptr refers. There should be no other owner.

  • If ptr is not the null pointer it should be a value returned by new because the destructor of the auto_ptr automatically calls delete for the object it owns.

  • Note that it is not correct to pass the return value of a new array that was creates by new []. (For arrays, the STL container classes, which are introduced in Section 5.2, should be used.)

Conversions

The rest of the class auto_ptr (auxiliary type auto_ptr_ref and functions using it) consists of rather tricky conversions that enable you to use copy and assignment operations for nonconstant auto_ptrs but not for constant auto_ptrs (see page 44 for details). The following is a quick explanation.[7] We have the following two requirements:

  1. It should be possible to pass auto_ptrs to and from functions as rvalues.[8] Because auto_ptr is a class, this must be done using a constructor.

  2. When an auto_ptr is copied, it is important that the source pointer gives up ownership. This requires that the copy modifies the source auto_ptr.

An ordinary copy constructor can copy an rvalue, but to do so it must declare its parameter as a reference to a const object. To use an ordinary constructor to copy an auto_ptr we would have to declare the data member containing the real pointer mutable so that it could be modified in the copy constructor. But this would allow you to write code that copies auto_ptr objects that were actually declared const, transferring their ownership in contradiction to their constant status.

The alternative is to find a mechanism to enable an rvalue to be converted to an lvalue. A simple operator conversion function to reference type does not work because an operator conversion function is never called to convert an object to its own type (remember that the reference attribute is not part of the type). Thus, the auto_ptr_ref class was introduced to provide this convert-to-lvalue mechanism. The mechanism relies on a slight difference between the overloading and template argument deduction rules. This difference is too subtle to be of use as a general programming tool, but it is sufficient to enable the auto_ptr class to work correctly.

Don't be surprised if your compiler doesn't support the distinction between nonconstant and constant auto_ptrs yet. And be aware that if your compiler does not yet implement this distinction, your auto_ptr interface is more dangerous. In this case, it is rather easy to transfer ownership by accident.

Sample Implementation of Class auto_ptr

The following code contains a sample implementation of a standard-conforming auto_ptr class[9]:

// util/autoptr.hpp
/* class auto_ptr
    *- improved standard conforming implementation
    */
   namespace std {
       //auxiliary type to enable copies and assignments (now global)
       template<class Y> 
       struct auto_ptr_ref {
           Y* yp; 
           auto_ptr_ref (Y* rhs)
            : yp(rhs) {
           }
       };


       template<class T>
       class auto_ptr {
         private:
           T* ap;    //refers to the actual owned object (if any)
         public:
           typedef T element_type;


           //constructor
           explicit auto_ptr (T* ptr = 0) throw()
            : ap(ptr) {
           }


           //copy constructors (with implicit conversion)
//- note: nonconstant parameter
           auto_ptr (auto_ptr& rhs) throw()
            : ap (rhs.release()) {
           }
           template<class Y>
           auto_ptr (auto_ptr<Y>& rhs) throw()
            : ap(rhs.release()) { 
           }


           //assignments (with implicit conversion)
//- note: nonconstant parameter
           auto_ptr& operator= (auto_ptr& rhs) throw() {
               reset(rhs.release());
               return *this; 
           }
           template<class Y>
           auto_ptr& operator= (auto_ptr<Y>& rhs) throw() {
               reset(rhs.release());
               return *this; 
           }


           //destructor
            ~auto_ptr() throw() {
                delete ap; 
           }


           //value access
           T* get() const throw() {
               return ap; 
           } 
           T& operator*() const throw() {
               return *ap; 
           }
           T* operator->() const throw() {
               return ap; 
           }


           //release ownership
           T* release() throw() {
               T* tmp(ap);
               ap = 0;
               return tmp;
           }


           //reset value
           void reset (T* ptr=0) throw(){
               if (ap != ptr) { 
                   delete ap; 
                   ap = ptr; 
               }
           }


           /* special conversions with auxiliary type to enable copies and assignments
*/
           auto_ptr(auto_ptr_ref<T> rhs) throw()
            : ap(rhs.yp) {
           }
           auto_ptr& operator= (auto_ptr_ref<T> rhs) throw() { //new 
                reset(rhs.yp);
                return *this;
           }
           template<class Y> 
           operator auto_ptr_ref<Y>() throw() {
               return auto_ptr_ref<Y>(release()); 
           }
           template<class Y> 
           operator auto_ptr<Y>() throw() {
               return auto_ptr<Y>(release()); 
           }
       };
   }

Numeric Limits

Numeric types in general have platform-dependent limits. The C++ standard library provides these limits in the template numeric_limits. These numeric limits replace and supplement the ordinary preprocessor constants of C. These constants are still available for integer types in <climits> and <limits.h>, and for floating-point types in <cfloat> and <float.h>. The new concept of numeric limits has two advantages: First, it offers more type safety. Second, it enables a programmer to write templates that evaluate these limits.

The numeric limits are discussed in the rest of this section. Note, however, that it is always better to write platform-independent code by using the minimum guaranteed precision of the types. These minimum values are provided in Table 4.1.

Table 4.1. Minimum Size of Built-in Types

Type Minimum Size
char 1 byte (8 bits)
short int 2 bytes
int 2 bytes
long int 4 bytes
float 4 bytes
double 8 bytes
long double 8 bytes

Class numeric_limits<>

Usually you use templates to implement something once for any type. However, you can also use templates to provide a common interface that is implemented for each type, where it is useful. You can do this by providing specialization of a general template. numeric_limits is a typical example of this technique, which works as follows:

  • A general template provides the default numeric values for any type:

       namespace std {
         /* general numeric limits as default for any type
    */
         template <class T>
         class numeric_limits {
           public:
             //no specialization for numeric limits exist
             static const bool is_specialized = false;
    
    
              ...  //other members that are meaningless for the general numeric limits
         };
       }

    This general template of the numeric limits says simply that there are no numeric limits available for type T. This is done by setting the member is_specialized to false.

  • Specializations of the template define the numeric limits for each numeric type as follows:

       namespace std {
          /* numeric limits for int
           * - implementation defined
    */
          template<> class numeric_limits<int> {
            public:
              //yes, a specialization for numeric limits of int does exist
              static const bool is_specialized = true;
    
    
              static T min() throw() {
                  return -2147483648;
              }
              static T max() throw() {
                  return 2147483647;
              }
              static const int digits = 31;
              ...
          };
       }

    Here, is_specialized is set to true, and all other members have the values of the numeric limits for the particular type.

The general numeric_limits template and its standard specializations are provided in the header file <limits>. The specializations are provided for any fundamental type that can represent numeric values: bool, char, signed char, unsigned char, wchar_t, short, unsigned short, int, unsigned int, long, unsigned long, float, double, and long double. They can be supplemented easily for user-defined numeric types.

Table 4.2 and Table 4.3 list all members of the class numeric_limits<> and their meanings. Applicable corresponding C constants for these members are given in the right column of the tables (they are defined in <climits>, <limits.h>, <cfloat>, and <float.h>).

Table 4.2. Members of Class numeric_limits<>, Part 1

Member Meaning C Constants
is_specialized Type has specialization for numeric limits  
is_signed Type is signed  
is_integer Type is integer  
is_exact Calculations produce no rounding errors (true for all integer types)  
is_bounded The set of values representable is finite (true for all built-in types)  
is_modulo Adding two positive numbers may wrap to a lesser result  
is_iec559 Conforms to standards IEC 559 and IEEE 754  
min() Minimum finite value (minimum normalized value for floating-point types with denormalization; meaningful if is_bounded || !is_signed) INT_MIN,FLT_MIN, CHAR_MIN,...
max() Maximum finite value (meaningful if is_bounded) INT_MAX,FLT_MAX,...
digits Character,Integer: number of nonsigned bits (binary digits) CHAR_BIT
Floating point: number of radix digits (see below) in the mantissa FLT_MANT_DIG,...
digits10 Number of decimal digits (meaningful if is_bounded) FLT_DIG,...
radix Integer: base of the representation (almost always two)  
 Floating point: base of the exponent representation FLT_RADIX
min_exponent Minimum negative integer exponent for base radix FLT_MIN_EXP,...
max_exponent Maximum positive integer exponent for base radix FLT_MAX_EXP,...
min_exponent10 Minimum negative integer exponent for base 10 FLT_MIN_10_EXP,...
max_exponent10 Maximum positive integer exponent for base 10 FLT_MAX_10_EXP,...
epsilon() Difference of one and least value greater than one FLT_EPSILON,...
round_style Rounding style (see page 63)  
round_error() Measure of the maximum rounding error (according to standard ISO/IEC 10967-1)  
has_infinity Type has representation for positive infinity  
infinity() Representation of positive infinity if available  
has_quiet_NaN Type has representation for nonsignaling "Not a Number"  
quiet_NaN() Representation of quiet "Not a Number" if available  
has_signaling_NaN Type has representation for signaling "Not a Number"  
signaling_NaN() Representation of signaling "Not a Number" if available  

Table 4.3. Members of Class numeric_limits<>,Part 2

Member Meaning C Constants
has_denorm Whether type allows denormalized values (variable numbers of exponent bits, see page 63)  
has_denorm_loss Loss of accuracy is detected as a denormalization loss rather than as an inexact result  
denorm_min() Minimum positive denormalized value  
traps Trapping is implemented  
tinyness_before Tinyness is detected before rounding  

The following is a possible full specialization of the numeric limits for type float, which is platform dependent. It also shows the exact signatures of the members:

   namespace std {
      template<> class numeric_limits<float> {
        public:
          //yes, a specialization for numeric limits of float does exist
          static const bool is_specialized = true;


          inline static float min() throw() {
              return 1.17549435E-38F; 
          } 
          inline static float max() throw() {
              return 3.40282347E+38F; 
          }


          static const int digits = 24;
          static const int digits10 = 6;


          static const bool is_signed = true;
          static const bool is_integer = false;
          static const bool is_exact = false;
          static const bool is_bounded = true;
          static const bool is_modulo = false;
          static const bool is_iec559 = true;


          static const int radix = 2;


          inline static float epsilon() throw() {
              return 1.19209290E-07F;
          }


          static const float_round_style round_style
              = round_to_nearest;
          inline static float round_error() throw() {
              return 0.5F;
          }


          static const int min_exponent = −125;
          static const int max_exponent = +128;
          static const int min_exponent10 = −37;
          static const int max_exponent10 = +38;


          static const bool has_infinity = true;
          inline static float infinity() throw() { return ...; }
          static const bool has_quiet_NaN = true;
          inline static float quiet_NaN() throw() { return ...; }
          static const bool has_signaling_NaN = true;
          inline static float signaling_NaN() throw() { return ...; }
          static const float_denorm_style has_denorm = denorm_absent;
          static const bool has_denorm_loss = false;
          inline static float denorm_min() throw() { return min(); }


          static const bool traps = true;
          static const bool tinyness_before = true; 
      };
   }

Note that all nonfunction members are constant and static so that their values can be determined at compile time. For members that are defined by functions, the value might not be defined clearly at compile time on some implementations. For example, the same object code may run on different processors and may have different values for floating values.

The values of round_style are shown in Table 4.4. The values of has_denorm are shown in Table 4.5. Unfortunately, the member has_denorm is not called denorm_style. This happened because during the standardization process there was a late change from a Boolean to an enumerative value. However, you can use the has_denorm member as a Boolean value because the standard guarantees that denorm_absent is 0, which is equivalent to false, whereas denorm_present is 1 and denorm_indeterminate is −1, both of which are equivalent to true. Thus, you can consider has_denorm a Boolean indication of whether the type may allow denormalized values.

Table 4.4. Round Style of numeric_limits<>

Round Style Meaning
round_toward_zero Rounds toward zero
round_to_nearest Rounds to the nearest representable value
round_toward_infinity Rounds toward positive infinity
round_toward_neg_infinity Rounds toward negative infinity
round_indeterminate Indeterminable

Table 4.5. Denormalization Style of numeric_limits<>

Denorm Style Meaning
denorm_absent The type does not allow denormalized values
denorm_present The type does allow denormalized values to the nearest representable value
denorm_indeterminate Indeterminable

Example of Using numeric_limits<>

The following example shows possible uses of some numeric limits, such as the maximum values for certain types and determining whether char is signed.

// util/limits1.cpp

   #include <iostream>
   #include <limits>
   #include <string>
   using namespace std;


   int main()
   {
      //use textual representation for bool
      cout << boolalpha;


      //print maximum of integral types
      cout << "max(short): " << numeric_limits<short>::max() << endl; 
      cout << "max(int): " << numeric_limits<int>::max() << endl; 
      cout << "max(long): " << numeric_limits<long>::max() << endl; 
      cout << endl;


      //print maximum of floating-point types
      cout << "max(float): "
           << numeric_limits<float>::max() << endl; 
      cout << "max(double): "
           << numeric_limits<double>::max() << endl; 
      cout << "max(long double): "
           << numeric_limits<long double>::max() << endl; 
      cout << endl;


      //print whether char is signed
      cout << "is_signed(char): "
           << numeric_limits<char>::is_signed << endl;
      cout << endl;


      //print whether numeric limits for type string exist
       cout << "is_specialized(string): "
            << numeric_limits<string>::is_specialized << endl; 
   }

The output of this program is platform dependent. Here is a possible output of the program:

   max(short): 32767
   max(int): 2147483647
   max(long): 2147483647


   max(float): 3.40282e+38
   max(double): 1.79769e+308
   max(long double): 1.79769e+308


   is_signed(char): false


   is_specialized(string): false

The last line shows that there are no numeric limits defined for the type string. This makes sense because strings are not numeric values. However, this example shows that you can query for any arbitrary type whether or not it has numeric limits defined.

Auxiliary Functions

The algorithm library (header file <algorithm>) includes three auxiliary functions, one each for the selection of the minimum and maximum of two values and one for the swapping of two values.

Processing the Minimum and Maximum

The functions to process the minimum and the maximum of two values are defined in <algorithm> as follows:

   namespace std {
       template <class T>
       inline const T& min (const T& a, const T& b) {
           return b < a ? b : a;
       }


       template <class T>
       inline const T& max (const T& a, const T& b) {
           return a < b ? b : a;
       }
   }

If both values are equal, generally the first element gets returned. However, it is not good programming style to rely on this.

Both functions are also provided with the comparison criterion as an additional argument:

   namespace std {
       template <class T, class Compare>
       inline const T& min (const T& a, const T& b, Compare comp) {
           return comp(b,a) ? b : a;
       }


       template <class T, class Compare>
       inline const T& max (const T& a, const T& b, Compare comp) {
           return comp(a,b) ? b : a;
       }
   }

The comparison argument might be a function or a function object that compares both arguments and returns whether the first is less than the second in some particular order (function objects are introduced in Section 5.9, page 124).

The following example shows how to use the maximum function by passing a special comparison function as an argument:

// util/minmax1.cpp

   #include <algorithm>
   using namespace std;


   /* function that compares two pointers by comparing the values to which they point
*/
   bool int_ptr_less (int* a, int* b)
   {
       return *a < *b;
   }


   int main()
   {
       int x = 17;
       int y = 42;
       int* px = &x;
       int* py = &y;
       int* pmax;


       //call max() with special comparison function
       pmax = max (px, py, int_ptr_less);
       ...
   }

Note that the definition of min() and max() require that both types match. Thus, you can't call them for objects of different types:

   int i;
   long l;
   ...
   1 = std::max(i,l) ;        //ERROR: argument types don't match

However, you could qualify explicitly the type of your arguments (and thus the return type):

   1 = std::max<long>(i,l) ;   //OK

Swapping Two Values

The function swap() is provided to swap the values of two objects. The general implementation of swap() is defined in <algorithm> as follows:

   namespace std {
       template<class T>
       inline void swap(T& a, T& b) {
           T tmp(a);
           a = b;
           b = tmp;
       }
   }

By using this function you can have two arbitrary variables x and y swap their values by calling

   std::swap(x,y);

Of course, this call is possible only if the copy constructions and assignments inside the swap() function are possible.

The big advantage of using swap() is that it enables to provide special implementations for more complex types by using template specialization or function overloading. These special implementations might save time by swapping internal members rather than by assigning the objects. This is the case, for example, for all standard containers (Section 6.1.2, page 147) and strings (Section 11.2.8, page 490). For example, a swap() implementation for a simple container that has only an array and the number of elements as members could look like this:

    class MyContainer {
      private:
        int* elems;        //dynamic array of elements
        int numElems;      //number of elements
      public:
        ...
        //implementation of swap()
        void swap(MyContainer& x) {
            std::swap(elems,x.elems);
            std::swap(numElems,x.numElems); 
        }
        ...
    };


    //overloaded global swap() for this type
    inline void swap (MyContainer& c1, MyContainer& c2)
    {
        c1. swap (c2);  //calls implementation of swap()
    }

So, calling swap() instead of swapping the values directly might result in substantial performance improvements. You should always offer a specialization of swap() for your own types if doing so has performance advantages.

Supplementary Comparison Operators

Four template functions define the comparison operators ! =, >, <=, and >= by calling the operators == and <. These functions are defined in <utility> as follows:

   namespace std {
       namespace rel_ops {
           template <class T>
           inline bool operator!= (const T& x, const T& y) {
               return !(x == y);

      bool operator== (const X& x) const;
      bool operator< (const X& x) const;
      ...
   };


   void foo()
   {
       using namespace std::rel_ops;     //make !=, >, etc., available
       X x1, x2;
      ...


      if (x1 != x2) {
          ...
      }
      ...


      if (x1 > x2) {
          ...
      }
      ...
   }

Note that these operators are defined in a subnamespace of std, called rel_ops. The reason that they are in a separate namespace is so that users who define their own relational operators in the global namespace won't clash even if they made all identifiers of namespace std global by using a general using directive:

   using namespace std;               //operators are not in global scope

On the other hand, users who want to get their hands on them explicitly can implement the following without having to rely on lookup rules to find them implicitly:

   using namespace std::rel_ops;     //operators are in global scope

Some implementations define the previous templates by using two different argument types:

   namespace std {
       template <class T1, class T2>
       inline bool operator!=(const T1& x, const T2& y) {
           return !(x == y); 
       }
       ...
   }

The advantage of such an implementation is that the types of the operands may differ (provided the types are comparable). But, note that this kind of implementation is not provided by the C++ standard library. Thus, taking advantage of it makes code nonportable.

Header Files <cstddef> and <cstdlib>

Two header files compatible with C are often used in C++ programs: <cstddef> and <cstdlib>. They are the new versions of the C header files <stddef.h> and <stdlib.h>, and they define some common constants, macros, types, and functions.

Definitions in <cstddef>

Table 4.6 shows the definitions of the <cstddef> header file. NULL is often used to indicate that a pointer points to nothing. It is simply the value 0 (either as an int or as a long). Note that in C, NULL often is defined as (void*)0. This is incorrect in C++ because there the type of NULL must be an integer type. Otherwise, you could not assign NULL to a pointer. This is because in C++ there is no automatic conversion from void* to any other type.[10] Note that NULL is also defined in the header files <cstdio>, <cstdlib>, <cstring>, <ctime>, <cwchar>, and <clocale>.

Table 4.6. Definitions in <cstddef>

Identifier Meaning
NULL Pointer value for "not defined" or "no value"
size_t Unsigned type for size units (such as number of elements)
ptrdiff_t Signed type for differences of pointer
offsetof() Offset of a member in a structure or union

Definitions in <cstdlib>

Table 4.7 shows the most important definitions of the <cstdlib> header file. The two constants EXIT_SUCCESS and EXIT_FAILURE are defined as arguments for exit(). They can also be used as a return value in main().

The functions that are registered by atexit() are called at normal program termination in reverse order of their registration. It doesn't matter whether the program exits due to a call of exit() or the end of main(). No arguments are passed.

Table 4.7. Definitions in <cstdlib>

Definition Meaning
exit (int status) Exit program (cleans up static objects)
EXIT_SUCCESS Indicates a normal end of the program
EXIT_FAILURE Indicates an abnormal end of the program
abort() Abort program (might force a crash on some systems)
atexit (void (*function)()) Call function on exit

The exit() and abort() functions are provided to terminate a program in any function without going back to main():

  • exit() destroys all static objects, flushes all buffers, closes all I/O channels, and terminates the program (including calling atexit() functions). If functions passed to atexit() throw exceptions, terminate() is called.

  • abort() terminates a program immediately with no clean up.

None of these functions destroys local objects because no stack unwinding occurs. To ensure that the destructors of all local objects are called, you should use exceptions or the ordinary return mechanism to return to and exit main().



[1] One could argue that numeric limits should be part of Chapter 12, which covers numerics, but these numeric limits are used in some other parts of the library, so I decided to describe them here.

[2] A template constructor does not hide the implicitly generated default constructor. See page 13 for more details about this topic.

[3] Using make_pair() should cost no runtime. The compiler should always optimize any implied overhead.

[4] This motivation of class auto_ptr is based, with permission, partly on Scott Meyers' book More Effective C++. The general technique was originally presented by Bjarne Stroustrup as the "resource allocation is initialization" in his books The C++ Programming Language, 2nd edition and The Design and Evolution of C++. auto_ptr was added to the standard specifically to support this technique.

[5] There is a minor difference between

    X x;
    Y y(x);   //explicit conversion

and

    X x;
    Y y = x;   //implicit conversion

The former creates a new object of type Y by using an explicit conversion from type X, whereas the latter creates a new object of type Y by using an implicit conversion.

[6] This is a slightly improved version that fixes some minor problems of the version in the C++ standard (auto_ptr_ref is global now and there is an assignment operator from auto_ptr_ref to auto_ptr; see page 55).

[7] Thanks to Bill Gibbons for pointing this out.

[8] The names rvalue and lvalue come originally from the assignment expression expr1 = expr2, in which the left operand expr1 must be a (modifiable) lvalue. However, an lvalue is perhaps better considered as representing an object locator value. Thus, it is an expression that designates an object by name or address (pointer or reference). Lvalues need not be modifiable. For example, the name of a constant object is a nonmodifiable lvalue. All expressions that are not lvalues are rvalues. In particular, temporary objects created explicitly (T()) or as the result of a function call are rvalues.

[9] Thanks to Greg Colvin for this implementation of auto_ptr. Note that it does not conform exactly to the standard. It turned out that the specification in the standard is still not correct regarding the special conversions encountered using auto_ptr_ref. The version presented in this book, hopefully, fixes all the problems. However, at the writing of this book, there was still ongoing discussion.

[10] Due to the mess with the type of NULL, several people and style guides recommend not using NULL in C++. Instead, 0 or a special user-defined constant such as NIL might work better. However, I use it, so you will find it in my examples in this book.

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

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