Algorithm Library Design: Course Home Page -- Lecture Notes -- Source Code -- References

5. Advanced C++ Programming


Introduction

C++ provides many ways of abstraction, ways to shield the user from implementation details. We will discuss several important concepts in this chapter: library initialization, const correctness, breaking cyclic type dependencies with templates, proxy classes, varies smart pointers, and double dispatch, a technique to make a function `virtual' for two arguments simultaniously.

However, it is useful to remember that all the protections in C++ require the cooperation from the user. John Lakos describes in [Lakos96] the extreme measures a developer team chose when the library they were using turned out to be too restrictive:

#define private   public
#define protected public
#define class     struct
The standard does not guarantee that this really works, but it is pretty effective and works probably with most compilers. The developers were well aware of their sin, but they were desperate.

We can distinguish between to kinds of protection a design can provide: protection against Murphy, and protection against Macchiavelly. Murphy describes an user that makes occasionally mistakes, while Macchiavelli describes an user that willingly tries to get around the protection mechanism. Protection against Macchiavelli in C++ is almost impossible, as C-style type casts and the example above illustrates. An effective but expensive solution would be opaque pointers and a link library manipulating the opaque pointer, where the sources of the underlying data are not published. Here, we discuss usually solutions that protect against Murphy.


Automatic Library Initialization and Housekeeping

In C

Initializers of global and static variables are automatic. But no function call can be triggered automatically in C, even none with the C-preprocessor. Libraries have to be initialized explicitly, if the initialization isn't trivial.

Depending on the C runtime library, it might be possible to register with atexit() or on_exit() during initialization a callback function that will be called upon the exit of the main function. Otherwise, housekeeping has also to be implemented as an explicit function call. (see also housekeeping.c)

In C++

We use a static member variable. Its default constructor initializes the library, its destructor performs housekeeping. However, an explicit housekeeping function might be appropriate to avoid the uncertainties about when the destructor gets finally called. The only restriction of this solution is that the initialization cannot rely on the initialization of static member variables of other classes. The order of initialization for non-local static objects is unspecified. Finding a feasible order would be too hard, in fact, Halting-Problem equivalent [Item 47, Meyers97] (this reference describes also how to get around this restriction with local static variables in global functions). Note, C++ initializes always non-local static objects, even if they are not used. But they have to be in a compilation unit that actually gets linked to the executable. Thus we write a header file with the following declaration and include it in each header that relies on the library being initialized. The static member variable count counts now how many different compilation units are initialized. The library is initialized for the first compilation unit, and housekeeping is performed once the last compilation unit destructs the static init_var (see also [Page 640, Stroustrup97] for this solution).

#ifndef INIT_H
#define INIT_H

class Init {
    static unsigned int count;
public:
    Init();
    ~Init();
};

// Trigger constructor and destructor calls in each compilation unit.
// Unnamed namespace can be used to avoid name collisions.
namespace {
    static Init _init_var; 
};

#endif // INIT_H //
There is a catch with this solution if we have a template library that does not have any object files for linking -- we need to link with an object file that contains the definition of the static member variable count and this one must only exist in one compilation unit. We implement also the constructor and the destructor to maintain the counter and to perform the initialization and housekeeping. (see also EX/Init.h and Init.C for a full implementation in the library example lib/)

unsigned int EX_Init::count = 0;

Init::Init() { // default constructor
    if ( 0 == count++) {
	// perform initialization
    }
}
Init::~Init() { // destructor
    if ( 0 == --count) {
	// perform housekeeping
    }
}
For a template library which consists only of header files, we can basically use the same class, but we make it a class template with a dummy template parameter which allows us to move the static member variable definition from the object file into the header file. (see EX/Template_init.h in the library example lib/).\


Const Correctness

(See also [Item 21 and 29, Meyers97].)

Const Declarations in C and C++

A const declaration of a variable forbids changes of the variable after its initialization.
const int i = 5;
// i = 6; // violates const declaration
A pointer can be declared const as well, thus the value of the pointer cannot change, but the value it refers to can change. As well, a pointer can be declared to point to a constant value. And both can be combined. Here is the table of all four combinations:
                       // the pointer,      the data it refers to
                       // ---------------------------------------
      int*       p;    // non-const         non-const
      int* const q;    // const             non-const
const int*       r;    // non-const         const
const int* const s;    // const             const
I read these declarations from the inside out. For example, for const int* const q I start with q the variable. const makes it constant. * makes it a pointer, thus a constant pointer. int makes it a constant pointer referring to a value of type int, and finally the left const declares the value of type int to be constant as well.

Member variables in classes can be declared constant as well. However, they can only be initialized with the constructor initializer syntax, not in the constructor call. An example:

struct A {
    const int i;
    A() : i(42) {}
};

Make Temporary Return Objects in C++ Classes Const

Member functions of classes in C++ have a hidden parameter this. For a class C, this parameter is of type T* const if the member function is declared non-const, or it is of type const T* const if the member function is declared const.
struct C {
    void foo();        // hidden parameter: T* const this;
    void bar() const;  // hidden parameter: const T* const this;
};
For built-in data types C and C++ distinguish between l-values and r-values. L-values can be used for the left side of an assignment, they are non-const. R-values cannot be used for the left side of an assignment. They can only be used for the right side of an assignment. They are const. For example the post-increment operator requires an l-value, but is itself an r-value. Thus, we cannot write:
int i;
i++ ++;  // second ++ forbidden!
For classes in C++ we need to model this behavior explicitly using const declarations. Consider the following class:
struct A {
    A operator++ (int);  // the post increment operator
};
Now, lets try:
A a;
a++++;
Fine, that works. It works, because a++ returns a temporary object of type A. But it probably does not what one would expect. Since the second ++ works on a temporary object, a itself gets only incremented once. We can forbid the second increment explicitly by making the return type, the type of the temporary object, const. This should be considered for all similar temporary return types.
struct A {
    const A operator++ (int);  // the post increment operator
};

Const Correctness and C++ Classes

Given an object of a class is declared const, the compiler guarantees bitwise constness, i.e., none of its non-static member variables can change its value. On the other hand, a user of a class expects conceptual constness which means that the value of an object cannot change its observable state if it is declared const.

Bitwise constness and conceptual constness are not the same. Two reasons: internal, not observable variables, and pointers.

There might be internal variables that can change but that cannot be observed. An example would be a string class that maintains a cache of the string length. The status of the cache cannot be observed from the outside (except in the runtime difference).

class string {
    char*   s;
    size_t  l;
    bool    valid;
public:
    string() : s(0), l(0), valid(true) {}
    size_t  length() const;  // const, does not change string conceptually
    ...  // some more functions modifying s and setting valid to false
};

size_t string::length() const {
    if ( ! valid) {
        l = strlen(s); // error, violates bitwise constness checked by the compiler!
        valid = true;  // error, violates bitwise constness checked by the compiler!
    }
    return l;
}
The new keyword mutable solves the problem here. It can be applied to a member variable to cancel out the const declaration. Changing the class definition as follows, the example from above works.
class string {
    char*           s;
    mutable size_t  l;
    mutable bool    valid;
public:
    string() : s(0), l(0), valid(true) {}
    size_t  length() const;  // const, does not change string conceptually
    ...  // some more functions modifying s and setting valid to false
};
The second problem of bitwise constness occurs with pointers and references. Bitwise constness of a pointer says that the pointer cannot change, but the referenced value can change. However, if the referenced value is considered to be part of the observable state, the referenced value should be considered constant as well. In the example of the string class, the pointer s cannot change, but the referenced character array could be changed. The implementor of the string class explicitly has to take care (and can take care) that this cannot happen. The key is that the class never exposes, i.e., returns, a non-const pointer or non-const reference to the array elements. Two examples: the array index operator to a single character element in the array, and a member function returning the raw character pointer. Note that we want to keep the full functionality for the non-const case as well. We implement both member functions twice, once for the const case, and once for the non-const case. (see also string.C)
class string {
    char*           s;
    mutable size_t  l;
    mutable bool    valid;
public:
    string() : s(0), l(0), valid(true) {}
    char&        operator[]( int idx)       { valid = false; return s[idx];}
    const char&  operator[]( int idx) const { return s[idx];}
    char* &      get_pointer()              { valid = false; return s; }
    const char*  get_pointer()        const { return s; }
    ...  // some more functions
};
Given this string class, we can use it safely without breaking const correctness.
int main() {
    char  buf[6] = "Hallo"; // German
    string s;
    s.get_pointer() = buf;
    assert( s[1] == 'a');
    s[1] = 'e';  // Now it's in English
    assert( s[1] == 'e');

    // get a const reference to the same string:
    const string& r = s;
    // r.get_pointer() = "Salute"; // does not work with const char *, r-value
    assert( r[1] == 'e');
    // r[1] = 'a'; // no, does not work with const char &
}

Proxy Classes

A dynamic two-dimensional array of integers could be declared in C++ as follows:
class Array2D {
public:
    Array2D( int dim1, int dim2);
    // ...
};
Of course, in a program we would like use the array similar to the builtin (static) two-dimensional arrays and access an element as follows:
int main() {
    Array2D a(5,10);
    // ...
    int i = a[2][8];
}
However, there is no operator[][] in C++. Instead, we can implement operator[] to return conceptually a one-dimensional array, where we can apply operator[] again to retrieve the element.
class Array1D {
public:
    Array1D( int dim);
    int operator[](int i);
    // ...
};
class Array2D {
public:
    Array2D( int dim1, int dim2);
    Array1D& operator[](int i);
    // ...
};
The intermediate class Array1D is called proxy class, also known as surrogate [Item 30, Meyers97]. Conceptually, it represents a one-dimensional array, but in this application we surely do not want to copy the elements to actually create a one-dimensional array. The proxy class will just behave as if it is an one-dimensional array and internally it will use a pointer to the two-dimensional array to implement its operations.

Another typical example for proxy classes is to distinguish between read and write access. The problem appears with string classes and their operator[] assuming the string class uses reference counting with 'copy-on-write' strategy.

struct string {
    const char& operator[](size_t index) const;
    char&       operator[](size_t index);
    // ...
};

int main() {
    string s = "Hello!";
    char c = s[3];
    s[5] = 'a';
}
Since s is not declared constant, the non-const index operator will be used for both uses of the operator[]. Since the string could be modified through this operator and it cannot see that the result of operator[] appears on the right side of the assignment in the first example, the string class must make the conservative assumption that it will be changed. We can defer this decision by introducing a proxy for the return type of operator[]:
struct string {
    const char& operator[] const;
    Proxy       operator[];
    // ...
}
The proxy distinguishes two cases: automatic conversion to a character r-value, which does not change the string, such as in the first example above, or the assignment of a character to the proxy, which corresponds to an l-value assignment, such as in the second example above. The proxy contains a reference to the string it belongs to:
class Proxy {
    string& str;
    int     index;
public:
    Proxy( string& s, int i) : str(s), index(i) {}
    // l-value uses, manipulate str if necessary
    Proxy& operator= ( const Proxy& rhs);
    Proxy& operator= ( char c);
    
    // r-value uses (str does not change) (automatic conversion to char)
    operator char() const;
}
Note that the client code does not change. Clients can pretend that operator[] returns a char in most cases.

Limitations pop up for other l-value uses of the proxy than assignment. For example, taking the address of the result of operator[], or the operators +=, -=, *= etc. However, all these operators can be implemented to behave correctly.

But in case we would deal with a class with member functions instead of a builtin type, we should also consider the distinction between constant and non-constant member functions. They have to be implemented in the Proxy as well, which changes the problem of writing a proxy from a moderate sized finite set of operators to a potentially infinite set of member functions for a general proxy for arbitrary types.

Another limitation is that proxies rely on an automatic conversion and the compiler can only use one automatic conversion to resolve expressions. The compiler cannot compose automatic conversions. Thus, some expressions that work without proxies do not work with proxies.

We have already seen the use of a proxy in the STL when implementing back_insert_iterator (see this Section). The back_insert_iterator is an output iterator and we use a proxy as a return type of operator*. Assigning a value to the proxy writes it to the output iterator, i.e., appends it to the underlying container in this case. Note that in this example the proxy class is actually the back_insert_iterator itself (for no good reason except to write less code).


Smart Pointers

A smart pointer is an object that behaves much like a pointer, but has some additional ``smart'' functionality. Smart pointer classes differ greatly in what additional functionality they provide and how closely they mimic built-in pointers. (For more information on smart pointers, see [Alexandrescu01, Chapter 7] or Boost smart pointer documentation.)

Smart pointers usually point to an object in dynamic memory and own it, that is, they are responsible of destroying and deleting the object when appropriate. This is the biggest deficiency of built-in pointers:

void f() {
    int* p = new int(42);
    // do something
    delete p;
}
This looks fine at first, but it can lead to a memory leak if the ``do something'' part of the code throws an exception (see Exception Safety) or contains something like:
    if (done) return;
Another context, where the failure of a built-in pointer to delete its pointee is problematic, is a container of pointers (see below).

Perhaps the most fundamental difference between various smart pointers is how they deal with copying. Consider:

void f() {
    SmartPtr p(new int(42));
    SmartPtr q = p;
}
The most obvious implementation would lead to deleting the same object twice, which is not acceptable. This is a big problem, because copying occurs in many, not always obvious places such as passing an argument by value, returning by value, and inserting to a standard container. The following three sections display four different approaches to copying smart pointers:

Smart Pointers: std::auto_ptr<T>

The standard library provides one smart pointer template, auto_ptr. It is meant as a replacement for built-in pointers for: It is not meant for containers of pointers.

The auto_ptr solves the copying problem with destructive copy (or move) semantics. In a copy operation, the new pointer obtains the ownership and the old pointer becomes a null pointer.

#include <memory>
using namespace std;

auto_ptr<int> source() { return auto_ptr<int>( new int(42)); }

void sink( auto_ptr<int> pt) {}

int main() {
    // these are legal and safe expressions with auto_ptr
    sink( source());
    auto_ptr<int> pt = source();
    sink( pt);
    pt = source();
    auto_ptr<int> pt2 = pt;
    // but this is not
    ++*pt;   // error: dereference of a null pointer
}
How about the following two lines?
std::vector< auto_ptr<int> > vec;
std::sort( vec.begin(), vec.end());
It depends, but most likely it does not work as expected. The reason is that the unusual copy semantics of auto_ptr<T> (modifies the right-hand side) does not comply with the requirements for the template parameters of STL container classes and algorithms.

How about the following line?

const auto_ptr<int> ptr( new int(42));
That is a truly const pointer. It cannot even be copied to another const auto_ptr<int>. This represents another approach to copying: no copying allowed. However, the value it refers to can be changed. This value could be declared constant as well, e.g., const auto_ptr<const int>. (See auto_ptr.C for the full example.)


Smart Pointers: Polymorphic Deep Copy

As auto_ptr is not suitable for containers, let us take a look at a smart pointer that is. This smart pointer helps in managing objects of a class hierarchy in a container class [Chapter 5, Koenig96]. We use a small hierarchy of shapes as example.
struct Shape {
    virtual Shape* clone() = 0;
    virtual ~Shape() {}
};
struct Circle : public Shape {
    virtual Circle* clone();
};
struct Square : public Shape {
    virtual Square* clone() = 0;
};
Container classes in the STL store objects by value. Thus, storing objects of type Shape wouldn't allow us to store objects of type Circle, they would be sliced to type Shape when we try to store them in the container.

Instead, we can store pointers to Shape in the container. With the lack of ownership of plain built-in pointers, we will use a smart pointer. For copying, we use deep-copy semantics: when a pointer is copied, so is the object it points to. Here, the virtual clone() function is provided for performing a polymorphic copy.

class ShapePtr {
    Shape* shape;
public:
    ShapePtr(Shape* s) : shape(s) {}
    ShapePtr(const ShapePtr& other) : shape(other->clone()) {}
    void swap(ShapePtr& other) { std::swap(shape, other.shape); }
    ShapePtr& operator= (const ShapePtr& rhs) {
        ShapePtr tmp(rhs);
        swap(tmp);
        return *this;
    }
    // still make it behave like a pointer
    Shape& operator*()  { return *shape; }
    Shape* operator->() { return shape; }
};

Smart Pointers: Reference Counting

The above solutions to the copying problem, destructive copy, no copy and deep copy, maintain the invariant that there is exactly one pointer pointing to each object. However, sometimes we want multiple pointers to share the same object. We distinguish between two such cases: One solution is to have just one owning pointer per objects. However, this requires a guarantee that the owning pointer lives longer than all the non-owning ones. A safer and more user-friendly alternative is reference counting. The pointed-to object has an associated counter keeping track of the number of pointers referring to it. The object is deleted when the counter drops to zero.

Reference counting smart pointers can be classified into intrusive (the counter is stored as a part of the object) and non-intrusive (the counter is stored separately). The non-intrusive variants can be used with any types of objects (just as ordinary pointers) but they cannot be implemented without some overhead. In the two non-intrusive variants illustrated below, the penalty is either large pointer or a more costly dereference operations. Furthermore, extra memory allocation is needed for the counter. Boost shared_ptr uses the design on the right.

The intrusive smart pointer requires modifying the pointed-to class to provide the counter. If we are in a position to design the class from scratch, it often makes sense to use handles instead of pointers. A handle does not behave like a pointer but like the object it points to. Instead of dereference operations it has all the public member functions of the pointed-to class. The pointed-to object (representation) contains the data members and the reference counter, but might not have any of the public member functions. For example, the standard string class is a handle.

The common functionality of handles and representations can be factored out into common base classes. Note that the handle is a class template parameterized by the representation it refers to. The representation is supposed to contain a count variable, for example, by deriving from the class Rep:

struct Rep {
    int  count;    
    Rep() : count( 1) {}
};

// Precondition: REP must have a member 'int count'.
template < class REP >
class Handle {
protected:
    // Invariant:  ptr is always != 0.
    REP* ptr;

public:
    Handle() : ptr( new REP) {}
    Handle( const REP& rep) : ptr( new REP(rep)) {}
    Handle( const Handle& x) : ptr(x.ptr) { ++ptr->count; }
    ~Handle() {
	if ( --ptr->count == 0) 
	    delete ptr;
    }    
    void swap(Handle& other) { std::swap(ptr, other.ptr); }
    Handle& operator= (const Handle& other) {
        Handle tmp(other);
        swap(tmp);
        return *this;
    }

};
The following example shows how to use these classes to implement a class for integers that uses reference counting (of course, reference counting does not pay off for plain integers).

struct Integer_rep : public Rep {
    int i;
    Integer_rep() {}
    Integer_rep( int j) : i(j) {}
};

class Integer : public Handle<Integer_rep> {
public:
    Integer() {}
    Integer( int i) : Handle<Integer_rep>(i) {}    
    int value() const  { return ptr->i; }
};
In a program, a handle can be used just as a normal type. (see also handle_ref.C for the full example, and handle_ref_extended.C for a more protected example, where the reference count is a private variable to protect the reference counting scheme from user code)

int main() {
    Integer a(5);
    Integer b(a);
    a = b;
    assert( b.value() == 5);
}
So far, the representation has been constant. Allowing modifications is a problem when the sharing in invisible, because user does not expect that the modification of one affects another. A solution is a strategy called copy-on-write. Before modifying the representation, the count is checked. If it is greater than 1, a new copy will be created in which the modification takes place.


Exception Safety

When an error occurs in a program, there are two possible responses: Often we want to do both. That is, we want to terminate a low level operation but recover and continue at a higher level. Then we need to transfer the execution from the point of error to the point of recovery. The two points may be separated by a long chain of function calls, which we call the error path. Some cleanup may be necessary on the error path: destroying objects, freeing dynamic memory, closing files, cancelling partial changes, etc.. The functions on the error path may come from different sources, including libraries.

Exceptions are a mechanism in C++ to handle this kind of error situations. An exception is an object (of an arbitrary type) that is thrown at the point of error and caught at the point of recovery. It may carry information about the error. All automatic (non-static) local objects on the error path are destroyed by calling their destructor. Functions on the error path may also catch the exception, perform cleanup, and re-throw the (same or a different) exception.

void error() {
    throw "error";            // point of error
}

void unsafe() {
    int* a = new int[100];  // leak!
    error();
    delete[] a;             // never executed
}

void safe_the_hard_way() {
    int* a = new int[100];  // safe now
    try {
        unsafe();
    } 
    catch (...) {   // catch anything
        delete[] a;
        throw;      // re-throw the original exception
    }
    delete[] a;
}

void safe_the_easy_way() {
    vector<int> v(100);            // safe: destructor cleans up
    auto_ptr<int> p(new int(42));  // safe: destructor cleans up
    safe_the_hard_way();
}

int main() {
    try {
        safe_the_easy_way();
    }
    catch (std::exception& e) {   // does not catch "error"
        cout << e.what() << endl; // but would catch std::bad_alloc
    }
    catch (const char* s) {   // point of recovery
        cout << s << endl;
    }
}
A function is exception-safe if it performs proper cleanup for any possible exception, and exception-neutral if it propagates all exceptions to the caller. There are three levels of exception safety:
  1. Basic guarantee: No resource leaks. The guarantee includes indirect resource leaks. For example, a member function may not leave an object in a state, where the destructor might not be able the free all resources.
  2. Strong guarantee: Commit-or-rollback. The function either completes the operation it was performing or leaves the program state unchanged as if it was never called.
  3. Nothrow guarantee: Never emits an exception.
It is difficult or impossible to make a function exception-safe and -neutral if it calls a function that is not exception-safe and -neutral. One weak link can destroy the guarantee. For this reason library code should be exception-safe and -neutral. Templates are particularly vulnerable since they know little about the exceptions the template parameters might throw.

Guideline: Resource Acquisition Is Initialization

The preferred way to achieve exception safety is to do resource acquisition (such as memory allocation) in a constructor and let the destructor take care of cleanup. This technique is frequently referred to as ``resource acquisition is initialization''. As demonstrated by the safe_the_easy_way() function above, standard library provides convenient facilities such as containers and auto_ptr for this. If the standard library facilities are not enough, it often makes sense to write a separate class just for managing a resource. Note: ``a resource'' (see next guideline).

Writing exception-safe constructors and destructors requires some extra care.

A destructor should never emit an exception. Recall that a destructor may be called during exception handling. A desctructor throwing in this situation terminates the program immediately. Furthermore, the destructor of a container (even a basic array) calls the destructor of all its elements. If one of the element destructors throws, the resulting behavior is undefined. Therefore, if a destructor has to do something that might throw, it should catch the exception:

X::~X() {
    try { 
        write_log("Destroying X");
    }
    catch (...) { }  // catch everything without re-throwing
}

If a constructor throws, the object remains partially constructed, which means that the destructor is never called.

class Person {
    Image* img;
    void init();
public:
    Person(const Image& i) 
      : img(new Image(i)) { init(); }  // leaks if init() throws
    ~Person() { delete image; }
};
Fully constructed subobjects (members and bases) are destroyed. Thus the simple cure here is to use auto_ptr:
class Person {
    auto_ptr<Image> img;
    void init();
public:
    Person(const Image& i) 
      : img(new Image(i)) { init(); }  // safe now
    ~Person() { }       // bonus: empty destructor
};

Guideline: Separate Responsibilities

A function or a class with multiple responsibilities is harder to make exception-safe than one with a single responsibility. Moving each responsibility to a different entity helps. For example, in the Person class above, moving the responsibility for the image memory to auto_ptr was the key.

Here is a function with two responsibilities: incrementing a counter and returning a value. Is it exception-safe?

int counter = 0;
string f(const Person& x) {
    ++counter;
    return x.name();
}
At the basic level, yes. At the strong level, no. If x.name() throws, the function does not complete its job (which includes returning a string), but the counter has been incremented. What about this?
int counter = 0;
string f(const Person& x) {
    string tmp = x.name();
    ++counter;
    return tmp;
}
Still not strongly exception-safe. Returning involves a string copy construction, which might throw. We can achieve strong guarantee by returning a pointer to string (copying a pointer cannot throw). A better alternative is to use auto_ptr:
int counter = 0;
auto_ptr<string> f(const Person& x) {
    auto_ptr<string> tmp(new string(x.name());
    ++counter;
    return tmp;
}
Here we achieved exception-safety by changing the return value of the function. This is often undesirable. For example, a stack operation pop() that both removes the top element and returns it, has a similar two-responsibilities problem, but changing the return type to auto_ptr is not really acceptable. For this reason, the STL stack (adapter) splits the two responsibilities between two function: top() returns the top element and pop() removes it. (See [Sutter99, pp. 25-54].)

Another point illustrated by these examples is that exception safety is not just a matter of implementation details: it can affect the interface. Thus exception safety should be considered early in the design process.

Guideline: Separate Throwing Code form Critical Code

Consider the following line.
    void f(auto_ptr<int>(new int(42)), g());
The order of evaluation of function arguments is not specified by the standard. In the above line, the evaluation order could be new int(42), g(), auto_ptr<int>(...). This could lead to memory leak if g() throws. The following is better.
    auto_ptr<int> (new int(42));
    void f(p, g());

More generally, it is useful to separate the code that might throw an exception from the code that performs critical operations, preferably doing the throwing code first. The throwing code could be performed on automatic local objects that get destroyed if an exception is thrown, effectively canceling the operation. A good example is the canonical copy assignment operator implemented in terms of a copy constructor and a swap:

struct A {
    // ...
    A(const A& other);                  // copy constructor
    void swap (const A& other) throw(); // swap *this with other
    A& operator= (const A& other) {     // copy assignment
        A tmp(other);     // if this throws, operation has no effect
        swap(tmp);        // can not throw
        return *this;
    }
    // ...
};
The only place in operator=, where an exception could be thrown, is the copy construction. If this happens, the operation exits without any permanent effect. Thus, we have the strong guarantee. The guarantee relies on the no-throw guarantee of swap. Usually swap performs a bitwise swap (for example, swapping pointers not pointees) and can be implemented using only copy operations on built-in types, which cannot throw.

The no-throw guarantee of swap was formalized in the exception specification throw(). In the general form, an exception specification lists the exception that might be thrown. Exception specifications should be used carefully. While they look similar to const specifications, they are less useful:

Note that a lack of formal exception specification in the code is not an excuse for omitting (a more informal) exception specification in the documentation.


Associating Data to Items in a Data Structure

Given a data structure with items, for example, a graph with nodes and edges, we want to associate an additional data field with each item. A typical application would be a boolean field for a graph traversal algorithm, depth first search. This application also highlights a possible dynamic nature of this associated fields, only a particular sub-algorithm might require them. Several solutions are possible:

LEDA graphs offer several of these possibilities: general purpose fields, templates, arrays, and hash arrays. A generic programming approach is taken by the Boost Graph Library [Siek01], where algorithms are implemented using property maps that hide the actual mechnism. For example:
template <class Vertex, class NameMap>
void foo(Vertex v, NameMap name) {
    typedef typename 
        boost::property_traits<NameMap>::value_type value_type;
    value_type oldname = get(name, v);   // get old name
    value_type newname = "New";
    put(name, v, newname);               // assign new name
    value_type& name_of_v = name[v];
    assert(name_of_v == newname);        // check the change
    name_of_v = oldname;                 // restore old name
}
The example illustrates the three operations on property maps: get(), put(), and operator[](). Even these are not provided for all property maps. There are four property map categories (similar to iterator categories):

Concept Refinement of Syntactic requirements
ReadablePropertyMap CopyConstructible get()
WritablePropertyMap CopyConstructible put()
ReadWritePropertyMap ReadablePropertyMap, WritablePropertyMap -
LvaluePropertyMap ReadWritePropertyMap operator[]()

For example, a boolean implemented as a bit in an integer cannot be a model of LvaluePropertyMap, because it is not possible to have a reference to it.


Solving Mutual Dependencies with the Barton-Nackman Trick

The following C++ technique is usually referred to as the Barton-Nackman trick since they have introduced it in their book [page 352, Barton97]. However, [Coplien95] documents some more occurrences of these "curiously recurring template patterns".

We start with a simple class that, among other operations, provides an equality and an inequality comparison operator.

class A {
public:
    bool operator == (const A& a) const;
    bool operator != (const A& a) const {
        return ! (*this == a);
    }
    // ...
};
The inequality comparison operator is implemented in terms of the equality operator. Wouldn't it be nice to factor out this generic implementation into a base class and share it with all classes of this kind? The problem is a cyclic type dependency. Of course, the (then) derived class A needs to know the base class. But the base class needs to know the derived class as well, since otherwise it cannot call the correct equality operator, and the derived class shows up in the operators type signature as well. Here, the solution is to inject the derived class as a template argument into the base class.
class A : public Inequality<A> {
public:
    bool operator == (const A& a) const;
};
Since we intend to call the equality operator of the derived class, we have to use a type cast, which is a safe down-cast in this case. (see also barton_nackman.C)
template <class T>
class Inequality {
public:
    bool operator != (const T& t) const {
        return ! (static_cast<const T&>(*this) == t);
    }
};
The same technique can be used to implement a base class for iterators that contains all those small member functions that are defined in terms of a much smaller set of member functions. Even better, since the base class is a template class, we can make use of the "lazy" implicit instantiation and implement the most general base class for iterators of the random access category. If we derive an iterator of the forward category, the extra random access operators in the base class are just ignored and don't cause error messages as long as they are not used. (see also Iterator_base.h and Iterator_base.C)

There is only one pitfall with this solution: name lookup rules for overloaded functions in class hierarchies. The name lookup stops as soon as the function name has been found, and it does not search for more overloaded function in base classes. The pre- and post-increment operator are an example (note also the use of a private member function to encapsulate the type cast, which is also const-overloaded):

template < class Derived>
class Iterator_base {
    Derived&       derived() { return static_cast<Derived&>(*this); }
    const Derived& derived() const { 
        return static_cast<const Derived&>(*this);
    }
public:
    const Derived  operator++(int) {
        Derived tmp = derived();
        ++ derived();
        return tmp;
    }
    // ...
};

class Some_iterator : public Iterator_base< Some_iterator> {
    // ...
public:
    Self& operator++();
    // ...
};

int main() {
    Some_iterator i;
    i++;
}
The post-increment call causes the compiler to look for the function name operator++ (without type signature for the arguments) in the class hierarchy. It finds the pre-increment operator in Some_iterator and stops with the function lookup. Only overloaded instances of the function in this class and global functions are now used to resolve the function call. The compiler does not find the correct post-increment operator in the base class and gives an error message. The solution is a workaround; implement all overloaded functions in the base class and give the involved functions in the derived class a new name, see Iterator_base.h.


Solving Mutual Dependencies between Class Templates

A graph consists of nodes and edges. A node knows incoming and outgoing edges, an edge knows its two incident nodes. Implementing this cyclic type dependencies between node and edge type would use a forward declaration in C:
struct Edge;

struct Node {
    Edge * edge;
    // .... maybe more than one edge ....
};

struct Edge {
    Node * source;
    Node * dest;
};
If we want to parameterize nodes and edges with a template parameter for some additional auxiliary data, we can follow the same idea. But since the node has to know the type of the edge, and the edge has to know the type of the node, both also have to know the template parameter of each other. A solution could look like this (see also graph.C for an example with an additional graph class).

// forward declaration
template <class A, class B>
struct Edge;

template <class A, class B>
struct Node {
    Edge<A,B> * edge;
    A aux;
};

template <class A, class B>
struct Edge {
    Node<A,B> * node;
    B aux;
};

int main() {
    Node<int,double> node;  // node with 
    Edge<int,double> edge;
    node.edge = &edge;
    edge.node = &node;
    node.aux = 5;
    edge.aux = 6.6;
}
One disadvantage of this solution is that the auxiliary data is always there and consumes space, even if it is not needed. It would be nice to specify it as void in this case. However, a local variable void aux is not going to work in C++. However, we can use partial specialization to write a specializes version, here for the node, to get rid of the reserved auxiliary data if we specify it to be void.
template <class B>
struct Node<void,B> {
    Edge<void,B> * edge;
};
In this solution the node type and the edge type are tightly coupled together (as in the C solution). In the spirit of generic programming we might want to decouple them. Imagine to exchange a vertex type Node with another vertex type Node' as in the following figure.

We might specify a concept for a node and a concept for an edge. Now, given a model for a node and a model for an edge they should work together in a graph class.

We use a similar mind-twister as for the Barton-Nackman trick in the previous section. The missing type information for a node as well as for the edge is provided with a template parameter Graph. There is a graph class that takes two parameters, a class template for a node and a class template for an edge (note the nested template declarators to pass a class template as a template argument). The graph class uses itself as parameter to the node template and the class template to define the local types for node and edge. To summarize, the graph knows the node and the edge class that are supposed to work together, and therefore the graph class passes itself as template argument to both types.

template <class Graph>
struct Node {
    typedef typename Graph::Edge Edge;
    Edge* edge;
    // .... maybe some more edges ....
};

template <class Graph>
struct Edge {
    typedef typename Graph::Node Node;
    Node* node;
};

template < template <class G> class T_Node, 
           template <class G> class T_Edge>
struct Graph {
    typedef Graph< T_Node, T_Edge>       Self;
    typedef T_Node<Self>                 Node;
    typedef T_Edge<Self>                 Edge;
};

int main() {
    typedef Graph< Node, Edge> G;
    G::Node node;
    G::Edge edge;
    node.edge = &edge;
    edge.node = &node;
}
To illustrate the flexibility in this design, we implement a new node class by deriving from the old one and add a member variable for color (see also graph2.C for this example, and graph3.C for a more extensive example).

template <class Graph>
struct Colored_node : public Node<Graph> {
    int color;
};

int main() {
    typedef Graph< Colored_node, Edge> G;
    G::Node node;
    G::Edge edge;
    node.edge = &edge;
    edge.node = &node;
    node.color = 3;
}
It is important to understand that these cyclic definitions work -- as for the C example -- because we can make use of a declared type to define pointers and references to this type before this type is defined itself. For example, we cannot change the pointer member Edge * edge of the node class to a value Edge edge.

Const correctness is an issue for a graph data structure. For example, declaring a node constant, it should not be possible to traverse the graph in any fashion and to reach a mutable node or edge. For the more extensive example graph3.C the adjacency list Edge_list edges is of particular interest. This list contains iterators to edges, but declaring the node constant, all access to the contents of this list has to return const_iterator's to edges.


Double Dispatch, Making a Function Virtual for Two Arguments

Imagine a game in space with ships, base stations and asteroids (see also [Item 31, Meyers96]). Collisions are handled as follows:

Ship Station Asteroid
Ship damage proportional to speed docking if slow, else damage destroys asteroid if it small, else ships get destroyed
Station docking if slow, else damage damage proportional to speed destroys asteroid if it small, else station get destroyed
Asteroid destroys asteroid if it small, else ships get destroyed destroys asteroid if it small, else station get destroyed asteroids break into smaller pieces

Assume all three objects are derived from a single abstract base class. We start with defining a virtual collision handling function that takes the second game object as a parameter.

struct Game_object {
    virtual void collision( Game_object* other) = 0;
    virtual ~Game_object() {}
};
struct Ship : public Game_object {
    virtual void collision( Game_object* other) {
        // this (of type Ship) collides here with other
    }
};
struct Station : public Game_object;  // similar
struct Asteroid : public Game_object;
Each collision knows the correct type of its this pointer, which resolves the first dispatch along the type of the first argument. But the other pointer is still of the abstract base class type, the second dispatch along the type of the second argument remains unresolved yet. We could use a switch/case statement to distinguish its actual type (using runtime type information (RTTI) with the typeid function). However, the object-oriented way doesn't like this. Switch/case statements tend to be unmaintainable and are not extendible. A possible object-oriented solution uses a set of virtual member functions to dipatch along the second argument (see double_dispatch_static.C for this example).

struct Game_object {
    virtual void collision( Game_object* other) = 0;
    virtual void collision2( Ship*     other) = 0;
    virtual void collision2( Station*  other) = 0;
    virtual void collision2( Asteroid* other) = 0;
    virtual ~Game_object() {}
};
struct Ship : public Game_object {
    virtual void collision( Game_object* other) {
        // this (of type Ship) collides here with other, call second dispatch
	other->collision2( this);
    }
    virtual void collision2( Ship*     other) {
        // Ship collides with ship.
    };
    virtual void collision2( Station*  other) {
        // Ship collides with Station.
    };
    virtual void collision2( Asteroid* other) {
        // Ship collides with Asteroid.
    };
};
struct Station : public Game_object  {};  // similar
struct Asteroid : public Game_object {};
If the class hierarchy is known and does not change in the future, this is the solution. But, this solution does not solve the problem of extendibility. Adding another game object, for example stars, would require to add a virtual member function to each class handling stars now.

Furthermore, we can observe that the collision handling is symmetric with respect to its two arguments. But we probably do not want to implement each function twice.

One extendible solution would be to implement double dispatch by hand as a (dynamic) array of function pointers that encode the collision handling table from above. We would implement the two-dimensional extension of the one-dimensional virtual function pointer table that used by the compiler to implement virtual member functions. Since we don't have the support of the compiler here (convinient index generation into this table etc.), the solution described at the end of [Item 31, Meyers96] is rather complicated and uses STL maps.

A simpler solution uses an if/then/else cascade to decide for each class using RTTI with which other classes it can handle the collisions. For unknown types, it calls the collision function again, but with the reversed order of arguments. Among the classes has to exist an order. A class has to implement the collision for all classes that are smaller in this order (in other words, a new class has to implement the collision with all old classes). There is the danger of missing to test for one class, in which case this solution produces an infinite loop (until the stack oveflows). This can be solved by breaking the symmetry, and give the second call with reversed parameters a different name (see double_dispatch_ext.C for this example).

We change the problem slightly and think of two different types that should interact with each other. An example would be a tree hierarchy composed of different node types, and a set of algorithm that work on the tree nodes. We can make this double dispatch problem extendible for either one of these types. We can just encode the algorithms as member functions in the different tree node types. Now it is easy to add a new tree node type. Or we can use the visotor pattern, see the Section Visitor Pattern, to make the family of algorithms extendible.

Templates could be used to solve the double dispatch problem conveniently if we change the setting slightly. Since templates work at compile time we have to know the actual types of colliding objects and we cannot work with base class pointers. The generic template implements the symmetry, the specialized functions implement the actual collision handling. This solution is extendible. A new class has to implement all possible combinations with old classes.

template < class T1, class T2>
void collision( T1& o1, T2& o2) {
    collision( o2, o1);
}
void collision( Ship&     o1, Ship&     o2);
void collision( Ship&     o1, Station&  o2);
void collision( Ship&     o1, Asteroid& o2);
void collision( Station&  o1, Station&  o2);
void collision( Station&  o1, Asteroid& o2);
void collision( Asteroid& o1, Asteroid& o2);

Lutz Kettner (<surname>@mpi-sb.mpg.de). Last modified on Tuesday, 17-Jan-2006 17:53:41 MET.