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).
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 pair
s 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 }
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.
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.
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).
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.
Functions often operate in the following way[4]:
Acquire some resources.
Perform some operations.
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
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_ptr
s must not own the same object at the same time. Unfortunately, it might happen that two auto_ptr
s own the same object (for example, if you initialize two auto_ptr
s 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_ptr
s 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_ptr
s own the same object. The solution is simple, but it has important consequences: The copy constructor and assignment operator of auto_ptr
s "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
The transfer of ownership implies a special use for auto_ptr
s; that is, functions can use them to transfer ownership to other functions. This can occur in two different ways:
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
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.
The semantics of auto_ptr
always include ownership, so don't use auto_ptr
s 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_ptr
s by reference instead. However, passing auto_ptr
s 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_ptr
s, 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_ptr
s 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_ptr
s 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_ptr
s, the assignment would transfer ownership into the container. However, because of the actual design of auto_ptr
s, 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_ptr
s 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 const
ness, 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.
By using auto_ptr
s 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_ptr
s:
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_ptr
s here if the auto_ptr
should not refer to the same object throughout its lifetime.
auto_ptr
s satisfy a certain need; namely, to avoid resource leaks when exception handling is used. Unfortunately, the exact behavior of auto_ptr
s changed in the past and no other kind of smart pointers are provided in the C++ standard library, so people tend to misuse auto_ptr
s. Here are some hints to help you use them correctly:
auto_ptr
s 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.
auto_ptr
s 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).
auto_ptr
s 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.)
auto_ptr
s 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_ptr
s 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.
The first example shows how auto_ptr
s 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_ptr
s 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_ptr
s 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_ptr
s. 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_ptr
s 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
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.
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.
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).
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.
delete
s 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.)
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_ptr
s but not for constant auto_ptr
s (see page 44 for details). The following is a quick explanation.[7] We have the following two requirements:
It should be possible to pass auto_ptr
s to and from functions as rvalues.[8] Because auto_ptr
is a class, this must be done using a constructor.
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_ptr
s 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.
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 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 |
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_limit
s 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 |
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.
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.
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
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.
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.
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.
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.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.
3.149.229.19