Chapter Ten

Object-oriented programming

In this chapter we will discuss rules and recommendations concerning the most important parts of object oriented programming, namely encapsulation, dynamic binding, inheritance and software contracts.

Encapsulation

There are many aspects to what is called encapsulation. For any data member it is required that the source code that may access it directly is limited to a part of the program, that can be deduced from inspecting the class definition only. The main idea is that users should not be affected by modifications to the class representation as long as the class interface is unchanged.

RULES
AND
RECOMMENDATIONS

Rule 10.1 Only declare data members private.

Rec 10.2 If a member function returns a pointer or reference, then you should document how it should be used and for how long it is valid.

Rec 5.5 , Rule 5.6 , Rec 5.7 , initialization of data members.

Rule 7.7 , return value of copy assignment operator.

Rule 10.1 Only declare data members private.

Public data members should be avoided. By only having private data members, it is possible to know in advance what code that modifies data members. This makes it less likely that the state of the object becomes corrupt by mistake.

We want to avoid having users that depend on the representation of the object. With public data members, it is difficult to predict how much code that must be modified when the representation changes. There will also always be a risk that the user modifies data members in a way not anticipated by the implementation of the class, creating bugs that are hard to find.

Imagine how hard it would be to maintain a class with public data members. Many bugs would most likely be the user's own fault, even though the program crashes inside member functions of the class. By declaring data members as private the effort to maintain the class will be less.

It is also impossible to change the name or type of public data members, since that would immediately break all code using them. If public data members are avoided, then the internal representation of a class can be changed without users of the class having to modify their code.

We also recommend that protected data members are avoided, since member functions of derived classes have the same kind of unrestricted and possibly dangerous access to protected data members as other functions have to public data members. Some might argue that constant members could be declared protected without risk, since these cannot be modified. Even here a member function interface is slightly better, since it makes the base class and the derived class more loosely coupled.

Rec 10.2 If a member function returns a pointer or reference, then you should document how it should be used and for how long it is valid.

Private data members are a good step towards encapsulation, but they are not enough. We must always document ownership and lifetime of objects that we return pointers or references to, and also any restrictions on how we use such pointers or references.

It is not always wrong to return a pointer or reference to an object, but if we have a badly designed class interface, it is possible that we use the object in a way that is not anticipated by the implementation.

Returning non-const reference to object

Suppose we have a string class with a member function length() that returns a non-const reference to the data member that stores the length of the string. Then we can easily invalidate the state of the object by assigning to the reference returned this way.

 
OtherString s("hello"); // length() == 5
s.length() = 114;       // Not recommended
                        // length() == 114

It is always unwise to give uncontrolled access to data that is part of an object's state. If such access is necessary, it is important that the user knows how to use the class correctly. A good design principle is to have as few limitations as possible on how to use a pointer or reference returned from a function.

It is not always wrong to return a pointer or reference to a data member, since not all such objects are part of the containing object's state. Sometimes it is even necessary to return a pointer or reference, e.g. when using overloaded operators to modify an object.

Assigning to string element

When assigning to an element of a string or an array, it is easier to read and understand the code if we use the same syntax as for built-in arrays.

EmcString has overloaded [] to allow assignment of individual elements of the string. This operator returns a reference to an array element that can be assigned to.

 
EmcString s = "Hello";
s[0] = 'h';               // Better than: s.set(0,'h');

Sometimes a function returns a pointer or a reference to an object that must be managed by the user. Typically the user must delete the object in order to avoid a memory leak. If a function transfers ownership of an object that it returns a pointer or reference to, then this must always be documented. A good strategy is to use a naming convention to make it obvious to the user when the object must be deleted by the user. You could e.g. give such functions a name that starts with " new ", " make " or " create ".

Dynamic binding

C++ allows you write code that only depends on a base class interface. It is possible to bind base class pointers or references to objects of derived classes and to operate on them without knowing their exact type. This makes it possible to add new derived classes without having to change the code that operates upon them. This makes programs easier to adapt to changing user requirements.

Here we want to explain how and when to use dynamic binding in your programs.

RULES
AND
RECOMMENDATIONS

Rec 10.3 Selection statements ( if and switch ) should be used when the flow of control depends on an object's value, while dynamic binding should be used when the flow of control depends on the object's type.

Rule 4.1 , Rec 4.2 - Rec 4.5 , writing if and switch statements.

Rec 10.3 Selection statements (if and switch) should be used when the flow of control depends on an object's value, while dynamic binding should be used when the flow of control depends on the object's type.

Heavy use of the selection statements if / else and switch might be an indication of a poor design. Selection statements should mostly be used when the flow of control depends on the value of an object.

Selection statements are not the best choice if the flow of control depends on the type of an object. If you want to have an extensible set of types that you operate upon, code that uses objects of different types will be difficult and costly to maintain. Each time you need to add a new type, each selection statement must be updated with a new branch. It is best to localize selection statements to a few places in the code. This however requires that you use inheritance and virtual member functions.

Suppose you have a class that is a public base class. It is possible to operate on objects of derived classes without knowing their type if you only call virtual member functions. Such member function calls are dynamically bound, i.e. the function to call is chosen in run-time. Dynamic binding is an essential component of object-oriented programming and we cannot overemphasize the importance that you understand this part of C++. You should try to use dynamic binding instead of selection statements as much as possible. It gives you a more flexible design since you can add classes without rewriting code that only depends on the base class interface.

Factory class

EmcCollection<T> is a base class that allows many different types of object collections to be manipulated through the same interface. It is only meant to be derived from and each derived class must override a set of pure virtual member functions. By making it an abstract base class, the class interface is more clearly separated from its implementation.

 
template <class T>
class EmcCollection
{
   public:
      // ...

      // insert one element
      virtual void insert(const T&) = 0; // pure virtual
      // ...

};

template <class T> 
ostream& 
operator<<(ostream&, const EmcCollection<T>& coll);

EmcArrayCollection is a class template derived from EmcCollection<T> that implements the base class interface. All pure virtual member functions are overridden so that an EmcArrayCollection<T> object can be created.

 
template <class T>
class EmcArrayCollection 
   : public virtual EmcCollection<T>
{
   public:
      static const size_t initialSize = 10;
      EmcArrayCollection(size_t maxsize = initialSize);
      // ...
};

A user of EmcCollectionFactory can create objects of classes derived from EmcCollection<T> without explicitly including their class definitions in the program, which makes the program less sensitive to changes in the implementation.

 
class InvalidCollectionType : public EmcException
{
   public:
      InvalidCollectionType(int id);
      // ...
   private:
      int idM;
};

template <class T>
class EmcCollectionFactory
{
   public:

      EmcCollectionFactory();
      // ...
      enum EmcCollectionId { ArrayId = 0, /* ... */ };
      virtual EmcCollection<T>* create(int type) const
         throw(InvalidCollectionType);
      virtual EmcCollection<T>* createArray() const;
      // ...
   private:
      // ...
};

Each class derived from EmcCollection<T> has its own type identifier represented as an integer. This identifier is passed to the create member function when creating an object.

 
EmcCollection<T>* 
EmcCollectionFactory<T>::create(int type) const
   throw(InvalidType)
{
   // Select behavior based on the value of type.

   switch (type)
   {
      case ArrayId:
      {
         return createArray();
      }
      // ...
      default:
      {
         throw InvalidCollectionType(type);
      }
   }
   return 0; // Never reached
}

template <class T>
EmcCollection<T>* 
EmcCollectionFactory<T>::createArray() const
{
   return new EmcArrayCollection<T>();
}

Dynamic binding

Suppose you have created an object of the class EmcArrayCollection<int> with a call to EmcCollectionFactory<int>::create() . That object can be assigned to an EmcCollection<int> pointer and operated upon using virtual member functions declared by the base class.

 
EmcCollectionFactory<int> factory;
EmcCollection<int>* collection =
   factory.create(EmcCollectionFactory<int>::ArrayId);

collection->insert(42); 
// EmcArrayCollection<int>::insert() is called

cout << *collection << endl;
delete collection; 
 

Inheritance

If you use inheritance, you need to plan in advance how the base class is meant to be used. Many base classes must have virtual destructors, but not all. Sometimes a base class should be declared virtual and sometimes not.

RULES
AND
RECOMMENDATIONS

Rule 10.4 A public base class must either have a public virtual destructor or a protected destructor.

Rule 10.5 If you derive from more than one base classes with the same parent, then that parent should be a virtual base class.

Rule 8.1 - Rule 8.2 , how to delete objects.

Rule 10.4 A public base class must either have a public virtual destructor or a protected destructor.

When a class appears as a public base class, derived classes should be specializations of the base class. This allows objects of derived classes to be operated upon through base class pointers or references. The user can use an object without knowing its exact type if a virtual member function is called.

The destructor is a member function that in most cases should be declared virtual . It is necessary to declare it virtual in a base class if derived class objects are deleted through a base class pointer. If the destructor is not declared virtual , only the base class destructor will be called when deleting an object that way. In addition to that, the size of the base class object will be passed to operator delete() and not the size of the complete object.

There is however a case where it is not appropriate to use virtual destructors; mix-in classes. Such a class is used to define a small part of an interface, which is inherited (mixed-in) by subclasses. In these cases the destructor, and hence also the possibility of a user deleting a pointer to such a mix-in base class, should normally not be part of the interface offered by the base class. The best thing to do in these cases is to have a non-virtual, non-public destructor, since that will not allow a user of a pointer to such a base class to claim ownership of the object and decide to simply delete it.

In such cases it is appropriate to make the destructor protected. This will stop users from accidentally deleting an object through a pointer to the mix-in base class, and therefore it is no longer necessary to require the destructor to be virtual.

Deleting a derived class object

EmcCollection<T> has a derived class EmcArrayCollection<T> that stores an array of T objects.

 
class EmcCollection
{
   public:
      // ...
      // destructor virtual for base class
      virtual ~EmcCollection();  
      // ...
};

template <class T>
class EmcArrayCollection : public virtual EmcCollection<T>
{
   public:
      // ...
      ~EmcArrayCollection();
      // ...
   private:
      size_t      indexM;
      EmcArray<T> arrayM;
      // ...
};

The destructor of the EmcArray<T> member must be called when the object ends its lifetime since otherwise memory allocated for the array will not be released. It is necessary to declare the destructor as virtual in the base class, if we want to be sure that the derived class object is properly deleted.

 
EmcCollectionFactory<int> factory;
EmcCollection<int>* collection =
   factory.create(EmcCollectionFactory<int>::ArrayId);
// ...
delete collection;

// 1. ~EmcArrayCollection<int>() is called
// 2. ~EmcArray<int>() is called
// 3. ~EmcCollection<int>() is called
// 4. ::operator delete(sizeof EmcArray<int>, cp) 
//    is called

The destructor for EmcArray would in this case never have been called if the destructor for EmcCollection had not been declared virtual .

Rule 10.5 If you derive from more than one base classes with the same parent, then that parent should be a virtual base class.

Multiple inheritance is a language feature that is seldom used, but it is for example very useful if you want to derive from classes in two different class libraries. It is then possible to have one derived class instead of many.

Each object of a derived class has an object representing each base class, a base class member. A problem with multiple inheritance is that when two base classes inherit from the same class, the default is to duplicate that base class member in the derived class, not to share it.

Why is this bad?

Since you actually have two base class objects, you cannot assign the derived class object to a pointer or reference to that base class.

You cannot call a member function introduced by that base class when directly operating upon objects of the derived class without explicitly qualifying the name with a base class name. When inheritance is non-virtual, all names that are introduced by the base class will be ambiguous. The presence of duplicated base classes will make a derived class different from other derived classes, which we should avoid.

It is more natural to share base class objects, but this requires that each base class that appears more than once as a base class, is a virtual base class.

Virtual base class

The class EmcLogged allows an object to write a log message on a format that is specified by the implementation of EmcLogged . It is meant to be used as a base class only and is an example of a mix-in base class.

 
class EmcLogged
{
   public:
      virtual void writeClassName(ostream&) const = 0;
      virtual void writeObjectId(ostream&) const;
      virtual void writeValue(ostream&) const = 0;

      void logMessage(const char* message) const;

   protected:
      ~EmcLogged(); // mix-in base class
};

The class has two pure virtual member functions that must be implemented by a derived class. They are called by the non-virtual member function logMessage() . This function prints a log message to a file.

Suppose we want to make it possible to write a collection to the log. We create a new class template EmcLoggedCollection that inherits from both EmcCollection<T> and EmcLogged , both which are declared virtual base classes.

 
template <class T>
class EmcLoggedCollection 
   : public virtual EmcCollection<T>,
     public virtual EmcLogged
{
   public:
      void writeValue(ostream&) const;

   protected:
      ~EmcLoggedCollection();
};

The member function writeValue() is implemented so that operator<<() is used to print a collection object. The class is abstract since it does not implement writeClassName() .

 
template <class T>
void EmcLoggedCollection<T>::writeValue(ostream& o) const
{
   o << *this;
}

Since both the EmcLogged destructor and the EmcLoggedCollection destructors are protected, we cannot delete objects through pointers of these types.

Since the EmcCollection<T> is a virtual base class, we can mix-in this behavior into another template derived from EmcArrayCollection<T> . Here, virtual inheritance is necessary since EmcCollection<T> appears as a base class more than once.

 
template <class T>
class EmcLoggedArrayCollection 
   : public virtual EmcArrayCollection<T>,
     public virtual EmcLoggedCollection<T>

{
   public:
      EmcLoggedArrayCollection();
      // ...
      virtual void writeClassName(ostream&) const;

   protected:
      ~EmcLoggedArrayCollection();
};

This class implements its constructors and its destructor so that a log message is written when these member functions are called. We could use this class when debugging our programs.

Inheritance can also be used to extend EmcCollectionFactory . Here, there is no need for virtual inheritance.

We create a class template EmcLoggedCollectionFactory that creates objects of classes that derive from EmcLoggedCollection<T> . The advantage of this approach is that we can trace how objects are created and deleted without changing the implementation of our existing EmcCollection classes. All that was required was the virtual inheritance from EmcCollection<T> .

 
template <class T>
class EmcLoggedCollectionFactory : public EmcCollectionFactory<T>
{
   public:
      virtual EmcCollection<T>* createArray() const;
};

template <class T>
EmcCollection<T>* EmcLoggedCollectionFactory<T>::createArray() const
{
   return new EmcLoggedArrayCollection<T>();
}

Since we only depend on the base class interface, we only need to change the type of the factory object that is created.

 
EmcLoggedCollectionFactory<int> factory;

EmcCollection<int>* collection =
   factory.create(EmcCollectionFactory<int>::ArrayId);

collection->insert(42);
// EmcLoggedArrayCollection<int>::insert() is called

// ...
delete collection; 
 
 

The Class Interface

When you design object-oriented systems, you must know how to describe class interfaces. Each class interface has member functions, types and relationships to other classes that must be described in a class specification.

The class specification should not only describe how the class should be implemented, but also how it should be used. The class specification is a software contract that must be obeyed by both the user of the class and the class supplier.

It is important to distinguish this external view of objects from their representation, since a class specification should not depend on any particular implementation of a class.

If a class appears as a public base class, the class specification is also valid for all its derived classes. Proper use of inheritance is important for good object-oriented design. Proper inheritance means that the interface of a public base class is also implemented correctly by derived classes. A derived class should not modify the base class interface, just extend it.

If C++ is used to describe preconditions, postconditions and class invariants, test programs will be much easier to write, and the specification will be more exact.

RULES
AND
RECOMMENDATIONS

Rec 10.6 Specify classes using preconditions, postconditions, exceptions and class invariants.

Rec 10.7 Use C++ to describe preconditions, postconditions and class invariants.

Rule 10.8 A pointer or reference to an object of a derived class should be possible to use wherever a pointer or reference to a public base class object is used.

Rec 10.9 Document the interface of template arguments.

Rule 11.1 , Rec 11.2 , assertions can be useful if you need to check conditions in your program.

Rec 10.6 Specify classes using preconditions, postconditions, exceptions and class invariants.

The program operates upon object by calling member functions. We want to write correct programs, which means that we must understand how to use the objects correctly. Unless we are careful, programming errors could result in unexpected run-time errors that terminate the program. We should also try to minimize the chance that a program relies on undocumented features.

A class specification should be the programmer's primary description of a class, that prevents us from making mistakes. The class specification should describe more than what you can get by reading the code and that is why we recommend you to provide preconditions, postconditions and exceptions for each member function.

The user must know under what conditions a member function is possible to call and if it has been implemented correctly.

The user's obligations are described as member function preconditions that describe under what circumstances a member function can be called.

Preconditions are conditions that should be valid on entry to a member function. Their purpose is to prevent an object from being used incorrectly.

The supplier's obligations are described as class invariants and member function postconditions. The class invariant describes conditions that are valid for all objects of the class.

Postconditions are conditions that should be valid on exit from a member function and their purpose is to specify how the state of an object is modified by a member function.

Pre- and postconditions

A stack is a classical example on an abstract data type with pre- and postconditions; here represented by the class EmcIntStack .

Initially a stack is empty. After you have pushed an element onto the stack, the stack is no longer empty. It is possible to push an element onto the stack as long as the stack is not full and to pop an element as long as the stack is not empty.

We can express this knowledge as pre- and postconditions of the corresponding member functions in the class.

 
class EmcIntStack
{
   public:
      // ...
      int  empty() const;
      int  full() const;
      int  top() const;
      void push(int i);
      int  pop();

   private:
      // ...
};

void EmcIntStack::push(int i)
{
   // Precondition:  ! full()
   // ...
   // Postcondition: ! empty()
}

int  EmcIntStack::pop()
{
   // Precondition: ! empty()
   // ...
}

Pre- and postconditions should always be valid, but what if they are not? The implementation of the member function should be written with the assumption that the precondition is valid, so it is the code that uses a class that must be modified if a precondition is not valid. This means that it is sometimes necessary to check the precondition before operating upon the object.

On the other hand, it is the implementation of a class that must be modified if a postcondition is not valid, since it is required that implementation makes the postcondition valid.

Using member function with precondition

 
EmcString makeString(const EmcIntStack& stack)
{
   EmcString returnValue;
   EmcIntStack copy(stack);
   ostrstream out;
   while (! copy.empty()) 
   // loop condition makes precondition valid
   {
      out << copy.pop();  // Precondition: ! copy.empty()
   }
   out << ends;
   char* buf = out.str();
   returnValue = buf;
   delete [] buf;
   return returnValue;
}

A class invariant could be seen as a set of conditions that must be valid for all objects of a class outside its member functions. Each public member function must leave the object in a state where the class invariant is valid. This means that the invariant should also be valid on entry to a member function.

Preconditions, postconditions and invariants are not part of the C++ language. Some languages such as Eiffel has explicit language support that allows the programmer to specify preconditions, postconditions and invariants using the programming language, but C++ does not have that.

Class with invariant

We could assume that the length of all EmcString objects are larger than 0 and equal to the length of the 0 -terminated string returned from cStr() . The latter assumption is however not correct, since this string class overloads [] that allows us to assign a 0 -character in the middle of the string. When specifying class invariants, we must make sure that it is difficult to break the invariant since that would make the class specification rather useless.

 
class EmcString
{
   public:
      // ...
      const char* cStr() const;
      // cStr() returns 0-terminated string
      size_t      length() const;
      char&       operator[](size_t index);
      // ...

      // Invariant:
      // length() >= 0

      // Not always true:
      // length() == ::strlen(cStr())
};

Rec 10.7 Use C++ to describe preconditions, postconditions and class invariants.

If it is possible, preconditions, postconditions and class invariants should be expressed as C++ expressions. Otherwise, the specification is open for human interpretation and will only rarely be an accurate description of the class. But there are a few exceptions. Some conditions are not possible to check inside a program or are too costly to check.

By using C++ to express conditions, and if the conditions are possible to check outside the scope of the class, test programs are easy to write. A good test program verifies both the specification and the implementation of a class. A program should behave the same with and without such checks, so it is inside such expressions essential to only observe properties of objects, not to modify them.

Normally, this means that the only member functions that should be called in such expressions are public accessors, since these should not modify the state of any objects. Constants and functions that does not modify any objects can also be used.

Using comments to specify class template

 
// EmcCollection is an abstract template class, 
// that allows a user to add, remove and search
// for objects within an arbitrary collection.

// REQUIRE(e), e is a precondition
// ENSURE(e), e is a postcondition
// throw(e), e is an exception type that an 
// implementation may throw

template <class T>
class EmcCollection
{
   public:

      virtual ~EmcCollection();

      // insert one element
      virtual void      insert(const T&) = 0; 
      // REQUIRE(! isFull())
      // ENSURE(! isEmpty())
      // throw(bad_alloc)

      // remove all elements
      virtual void      clear() = 0;          
      // ENSURE(isEmpty())

      // ...

      // Remove one element
      virtual T         remove() = 0;         
      // REQUIRE(!isEmpty())

      // ...
};

The member function insert() has a precondition; the collection must not be full when inserting an object. This condition is possible to check by calling the accessor member function isFull() . It also has a postcondition; the collection must not be empty after an element has been inserted.

It would have been reasonable to further specify the postconditions by saying that the size of the collection grows by 1 each time an element is inserted. By not doing that, it is possible to have a collection that grows until it is full and then simply refuses to insert more elements. It is intentional not to have such a postcondition, since that allows us to specify how a derived class is allowed to make the postcondition stronger.

Checking precondition

 
EmcCollectionFactory<int> factory;
EmcCollection<int>* collection =    
   factory.create(EmcCollectionFactory<int>::ArrayId);

if (! collection->isFull())
{
   collection->insert(42);
   // ...
} 

Rule 10.8 A pointer or reference to an object of a derived class should be possible to use wherever a pointer or reference to a public base class object is used.

A class inherit from another class either to reuse the implementation or the class interface. Public inheritance makes it possible to write code that only depends on the base class interface, not the implementation. Public inheritance should only be used if derived class objects are supposed to be operated upon through base class pointers or references.

You should reconsider the way inheritance is used, if it is dangerous to call inherited member functions for a derived class object. Such member functions can either be called directly by the base class implementation, or indirectly when the object is accessed through a base class pointer or reference.

Substitutability is a property of derived classes that will allow you to use objects of these classes without changing code that depends on the base class interface only. If a virtual member function has a precondition and a postcondition, then these must be valid for all implementations of the class interface. If they are not, the derived class should not inherit the base class.

Substitutability

 
// insertObject() works for any class with
// EmcCollection<T> as public base class.

template <class T>
bool insertObject(EmcCollection<T>& c, const T& element)
// throw (bad_alloc)
{
   // return false if insertion fails, true otherwise

   if (! c.isFull())
   {
      c.insert(element);
      return true;
   }
   return false;
}

FOOTNOTE: It is worth noting that this function does not have an exception specification. The main reason is that we want to allow any EmcCollection instantiations to use this function. It could be possible that an exception is thrown when the inserted element is copied. Since its type is unknown, we cannot know what exceptions that are thrown.

Typically, an implementation of a virtual member function in a derived class can allow the member function to be called in more situations than specified by the base class, so the precondition can be weaker in a derived class. The opposite, a stronger precondition, breaks substitutability.

A derived class implementation often does more than the postcondition of the base class promises, because the implementation has added state that is also modified. The opposite, a weaker postcondition, breaks substitutability.

Substitutability also requires that a derived class always fulfils the base class invariant. Otherwise an object can be put in a state that is not expected by the user of the class.

Specification of overriden member function

A collection may be bounded or unbounded, so it is natural to specialize the base class EmcCollection<T> .

The class template, EmcBoundedCollection , represents a family of classes derived from an EmcCollection -instantiation, that only allows a limited number of objects to be inserted. By pre-allocating storage, it is possible to avoid a bad_alloc exception when an object is inserted. This a stronger promise than made by the base class, but that does not break substitutability, since the precondition for insert() is the same.

 
   virtual void insert(const T&); 
   // REQUIRE(! isFull())
   // ENSURE(! isEmpty())

The class template, EmcUnboundedCollection represents a family of classes derived from a EmcCollection -instantiation, that allows any number of objects to be inserted. As long as the program does not run out of memory, objects can be inserted, i.e. the precondition is weaker, but the postcondition is still valid.

 
   virtual void insert(const T&); 
   // throw(bad_alloc)
   // ENSURE(! isEmpty())
   // ENSURE(OLD.size() + 1 == size())

On the other hand, a stronger postcondition has been added. An insertion must increase the size of the collection or throw a bad_alloc exception. The old postcondition that the collection is not empty after an insertion is a consequence of this new stronger postcondition, since the size will always be larger than 0 . It is mentioned here for exposure only.

Without this stronger postcondition, an implementation could simply overwrite stored objects instead of increasing the size of the collection. That is a behavior that the user probably does not expect when operating on an unbounded collection. A derived class should give additional constraints for how the base class interface is implemented.

 
// insertObject() works for any class with 
// EmcUnboundedCollection<T> as a public base class.

template <class T>
bool insertObject(EmcUnboundedCollection<T>& cref,
                  const T& element)  // throw (bad_alloc)
{
   // return false if insertion fails, true otherwise

   // The precondition of 
   // EmcUnboundedCollection<T>::insert is weaker than the 
   // precondition for EmcCollection<T>::insert since an 
   // unbounded collection is never full.

   cref.insert(element);
   return true;
} 

Rec 10.9 Document the interface of template arguments.

A template defines a family of classes or functions. Apart from having template parameters that must be given values before it is used, a template is not very different from an ordinary class or function. Here we discuss what is different with templates; the presence of type parameters and the consequence of having classes and functions that are generated by the compiler. This will also help you both when you want to write you own templates and when you only want to use templates.

Templates were originally introduced in C++ to make it possible to write type safe containers without having to use macros to change the stored type.

Describing template argument requirements

EmcCollection is a class template whose instantiations are abstract classes.

 
// T must be: DefaultConstructible
//            CopyConstructible
//            Assignable
//            Destructible
//            EqualityComparable

template <class T>
class EmcCollection
{
   public:
      // ...
};

We have a comment to describe what is required for the type argument T in order to instantiate the template.

These requirements must be known to the user of the class. By having symbolic names for the most common requirements, the specification of template requirements will be shorter and easier to comprehend.

In the example above, we use names that are taken from the C++ standard library and they have the following meaning.

If T is a type, the following expressions should be valid.

 
   T t1;                // DefaultContructible
   T t2(t1);            // CopyConstructible
   t2 = t1;             // Assignable
   bool b = (t2 == t1); // EqualityComparable
   // Destructable, an object on the stack can be created.

An appropriate way to extend the basic interface requirements is to simply say, for example:

" T must have: int T::hash() const "

The compiler checks that a template argument is suitable. For class templates, only those member function templates that are actually used will be instantiated. Some older compilers instantiate the whole class, but that is not standard behavior. A consequence is that a class template can be used with arguments that only fulfill a subset of the requirements, as long as member functions that require more are not used. This is not a recommended use of a class template, since it is an implementation detail to know how the requirements are related to individual member functions.

To make sure that the template arguments are well-behaved, the class should have a private static member function that contains expressions that can only be parsed if the template arguments fulfill the complete set of requirements.

If this member function is instantiated, the full set of requirements will be checked by the compiler.

Checking type constraints

 
template <class T>
class EmcCollection
{
   public:
      // ...
      static void templateRequirements();
      // ...
};

template <class T>
void EmcCollection<T>::templateRequirements()
{
                        // T must be:
   T t1;                // DefaultContructible
   T t2(t1);            // CopyConstructible
   t2 = t1;             // Assignable
   bool b = (t2 == t1); // EqualityComparable
}                       // Destructible

These checks does not help you to determine the performance characteristics of a type. If types with the wrong characteristics are used, the program may perform very poorly. By documenting the time-complexity for different operations on the instantiation-bound types, the user will be able to avoid surprises.

A template instantiation could also have a set of types that are found by qualifying their name with template type parameters. These must also be taken in consideration when specifying templates.

Performance characteristics of types

A container in the standard library should provide the following two types:

value_type

Type of values stored by the container.

iterator

For access to objects in container.

The first type, value_type , is assumed to be costly to copy, since any value should be possible to store in a container.

The second type, iterator , should behave as a pointer and is therefore assumed to be cheap to copy.

The consequence of this is that value_type object are always passed as const references, while iterator objects are passed as values.