[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

16.2 Changeable C++

The C++ standard encompasses the language and the interface to the standard library, including the Standard Template Library (see section Standard Template Library). The language has evolved somewhat since the ARM was published; mostly driven by the experience of early C++ users.

In this section, the newer features of C++ will be briefly explained. Alternatives to these features, where available, will be presented when compiler support is lacking. The alternatives may be used if you need to make your code work with older C++ compilers or to avoid these features until the compilers you are concerned with are mature. If you are releasing a free software package to the wider community, you may need to specify a minimum level of standards conformance for the end-user’s C++ compiler, or use the unappealing alternative of using lowest-common denominator C++ features.

In covering these, we’ll address the following language features:


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

16.2.1 Built-in bool type

C++ introduced a built-in boolean data type called bool. The presence of this new type makes it unnecessary to use an int with the values 0 and 1 and improves type safety. The two possible values of a bool are true and false–these are reserved words. The compiler knows how to coerce a bool into an int and vice-versa.

If your compiler does not have the bool type and false and true keywords, an alternative is to produce such a type using a typedef of an enumeration representing the two possible values:

 
enum boolvals { false, true };
typedef enum boolvals bool;

What makes this simple alternative attractive is that it prevents having to adjust the prolific amount of code that might use bool objects once your compiler supports the built-in type.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

16.2.2 Exceptions

Exception handling is a language feature present in other modern programming languages. Ada and Java both have exception handling mechanisms. In essence, exception handling is a means of propagating a classified error by unwinding the procedure call stack until the error is caught by a higher procedure in the procedure call chain. A procedure indicates its willingness to handle a kind of error by catching it:

 
void foo ();

void
func ()
{
  try {
    foo ();
  }
  catch (...) {
    cerr << "foo failed!" << endl;
  }
}

Conversely, a procedure can throw an exception when something goes wrong:

 
typedef int io_error;

void
init ()
{
  int fd;
  fd = open ("/etc/passwd", O_RDONLY);
  if (fd < 0) {
    throw io_error(errno);
  }
}

C++ compilers tend to implement exception handling in full, or not at all. If any C++ compiler you may be concerned with does not implement exception handling, you may wish to take the lowest common denominator approach and eliminate such code from your project.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

16.2.3 Casts

C++ introduced a collection of named casting operators to replace the conventional C-style cast of the form (type) expr. The new casting operators are static_cast, reinterpret_cast, dynamic_cast and const_cast. They are reserved words.

These refined casting operators are vastly preferred over conventional C casts for C++ programming. In fact, even Stroustrup recommends that the older style of C casts be banished from programming projects where at all possible The C++ Programming Language, 3rd edition. Reasons for preferring the new named casting operators include:

If your compiler does not support the new casting operators, you may have to continue to use C-style casts—and carefully! I have seen one project agree to use macros such as the one shown below to encourage those involved in the project to adopt the new operators. While the syntax does not match that of the genuine operators, these macros make it easy to later locate and alter the casts where they appear in source code.

 
#define static_cast(T,e) (T) e

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

16.2.4 Variable Scoping in For Loops

C++ has always permitted the declaration of a control variable in the initializer section of for loops:

 
for (int i = 0; i < 100; i++)
{
  ...
}

The original language specification allowed the control variable to remain live until the end of the scope of the loop itself:

 
for (int i = 0; i < j; i++)
{
  if (some condition)
    break;
}

if (i < j)
  // loop terminated early

In a later specification of the language, the control variable’s scope only exists within the body of the for loop. The simple resolution to this incompatible change is to not use the older style. If a control variable needs to be used outside of the loop body, then the variable should be defined before the loop:

 
int i;

for (i = 0; i < j; i++)
{
  if (some condition)
    break;
}

if (i < j)
  // loop terminated early

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

16.2.5 Namespaces

C++ namespaces are a facility for expressing a relationship between a set of related declarations such as a set of constants. Namespaces also assist in constraining names so that they will not collide with other identical names in a program. Namespaces were introduced to the language in 1993 and some early compilers were known to have incorrectly implemented namespaces. Here’s a small example of namespace usage:

 
namespace Animals {
  class Bird {
  public:
    fly (); {} // fly, my fine feathered friend!
  };
};

// Instantiate a bird.
Animals::Bird b;

For compilers which do not correctly support namespaces it is possible to achieve a similar effect by placing related declarations into an enveloping structure. Note that this utilises the fact that C++ structure members have public protection by default:

 
struct Animals {
  class Bird {
  public:
    fly (); {} // fly, my find feathered friend!
  };
protected
  // Prohibit construction.
  Animals ();
};

// Instantiate a bird.
Animals::Bird b;

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

16.2.6 The explicit Keyword

C++ adopted a new explicit keyword to the language. This keyword is a qualifier used when declaring constructors. When a constructor is declared as explicit, the compiler will never call that constructor implicitly as part of a type conversion. This allows the compiler to perform stricter type checking and to prevent simple programming errors. If your compiler does not support the explicit keyword, you should avoid it and do without the benefits that it provides.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

16.2.7 The mutable Keyword

C++ classes can be designed so that they behave correctly when const objects of those types are declared. Methods which do not alter internal object state can be qualified as const:

 
class String
{
public:
  String (const char* s);
  ~String ();

  size_t Length () const { return strlen (buffer); }

private:
  char* buffer;
};

This simple, though incomplete, class provides a Length method which guarantees, by virtue of its const qualifier, to never modify the object state. Thus, const objects of this class can be instantiated and the compiler will permit callers to use such objects’ Length method.

The mutable keyword enables classes to be implemented where the concept of constant objects is sensible, but details of the implementation make it difficult to declare essential methods as const. A common application of the mutable keyword is to implement classes that perform caching of internal object data. A method may not modify the logical state of the object, but it may need to update a cache–an implementation detail. The data members used to implement the cache storage need to be declared as mutable in order for const methods to alter them.

Let’s alter our rather farfetched String class so that it implements a primitive cache that avoids needing to call the strlen library function on each invocation of Length ():

 
class String
{
public:
  String (const char* s) :length(-1) { /* copy string, etc. */ }
  ~String ();

  size_t Length () const
  {
    if (length < 0)
      length = strlen(buffer);
    return length;
  }

private:
  char* buffer;
  mutable size_t length;
}

When the mutable keyword is not available, your alternatives are to avoid implementing classes that need to alter internal data, like our caching string class, or to use the const_cast casting operator (see section Casts) to cast away the ‘constness’ of the object.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

16.2.8 The typename Keyword

The typename keyword was added to C++ after the initial specification and is not recognized by all compilers. It is a hint to the compiler that a name following the keyword is the name of a type. In the usual case, the compiler has sufficient context to know that a symbol is a defined type, as it must have been encountered earlier in the compilation:

 
class Foo
{
public:
  typedef int map_t;
};

void
func ()
{
  Foo::map_t m;
}

Here, map_t is a type defined in class Foo. However, if func happened to be a function template, the class which contains the map_t type may be a template parameter. In this case, the compiler simply needs to be guided by qualifying T::map_t as a type name:

 
class Foo
{
public:
  typedef int map_t;
};

template <typename T>
void func ()
{
  typename T::map_t t;
}

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

16.2.9 Runtime Type Identification (RTTI)

Run-time Type Identification, or RTTI, is a mechanism for interrogating the type of an object at runtime. Such a mechanism is useful for avoiding the dreaded switch-on-type technique used before RTTI was incorporated into the language. Until recently, some C++ compilers did not support RTTI, so it is necessary to assume that it may not be widely available.

Switch-on-type involves giving all classes a method that returns a special type token that an object can use to discover its own type. For example:

 
        class Shape
        {
        public:
          enum types { TYPE_CIRCLE, TYPE_SQUARE };
          virtual enum types type () = 0;
        };
        class Circle: public Shape
        {
        public:
         enum types type () { return TYPE_CIRCLE; }
        };
        class Square: public Shape
        {
        public:
          enum types type () { return TYPE_SQUARE; }
        };

Although switch-on-type is not elegant, RTTI isn’t particularly object-oriented either. Given the limited number of times you ought to be using RTTI, the switch-on-type technique may be reasonable.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

16.2.10 Templates

Templates—known in other languages as generic types—permit you to write C++ classes which represent parameterized data types. A common application for class templates is container classes. That is, classes which implement data structures that can contain data of any type. For instance, a well-implemented binary tree is not interested in the type of data in its nodes. Templates have undergone a number of changes since their initial inclusion in the ARM. They are a particularly troublesome C++ language element in that it is difficult to implement templates well in a C++ compiler.

Here is a fictitious and overly simplistic C++ class template that implements a fixed-sized stack. It provides a pair of methods for setting (and getting) the element at the bottom of the stack. It uses the modern C++ template syntax, including the new typename keyword (see section The typename Keyword).

 
template <typename T> class Stack
{
public:
  T first () { return stack[9]; }
  void set_first (T t) { stack[9] = t; }

private:
  T stack[10];
};

C++ permits this class to be instantiated for any type you like, using calling code that looks something like this:

 
int
main ()
{
  Stack<int> s;
  s.set_first (7);
  cout << s.first () << endl;
  return 0;
}

An old trick for fashioning class templates is to use the C preprocessor. Here is our limited Stack class, rewritten to avoid C++ templates:

 
#define Stack(T) \
  class Stack__##T##__LINE__ \
  { \
  public: \
    T first () { return stack[0]; } \
    void set_first (T t) { stack[0] = t; } \
  \
  private: \
    T stack[10]; \
  }

There is a couple of subtleties being used here that should be highlighted. This generic class declaration uses the C preprocessor operator ‘##’ to generate a type name which is unique amongst stacks of any type. The __LINE__ macro is defined by the preprocessor and is used here to maintain unique names when the template is instantiated multiple times. The trailing semicolon that must follow a class declaration has been omitted from the macro.

 
int
main ()
{
  Stack (int) s;
  s.set_first (7);
  cout << s.first () << endl;
  return 0;
}

The syntax for instantiating a Stack is slightly different to modern C++, but it does work relatively well, since the C++ compiler still applies type checking after the preprocessor has expanded the macro. The main problem is that unless you go to great lengths, the generated type name (such as Stack__int) could collide with other instances of the same type in the program.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

16.2.11 Default template arguments

A later refinement to C++ templates was the concept of default template arguments. Templates allow C++ types to be parameterized and as such, the parameter is in essence a variable that the programmer must specify when instantiating the template. This refinement allows defaults to be specified for the template parameters.

This feature is used extensively throughout the Standard Template Library (see section Standard Template Library) to relieve the programmer from having to specify a comparison function for sorted container classes. In most circumstances, the default less-than operator for the type in question is sufficient.

If your compiler does not support default template arguments, you may have to suffer without them and require that users of your class and function templates provide the default parameters themselves. Depending on how inconvenient this is, you might begrudgingly seek some assistance from the C preprocessor and define some preprocessor macros.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

16.2.12 Standard library headers

Newer C++ implementations provide a new set of standard library header files. These are distinguished from older incompatible header files by their filenames—the new headers omit the conventional ‘.h’ extension. Classes and other declarations in the new headers are placed in the std namespace. Detecting the kind of header files present on any given system is an ideal application of Autoconf. For instance, the header ‘<vector>’ declares the class std::vector<T>. However, if it is not available, ‘<vector.h>’ declares the class vector<T> in the global namespace.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

16.2.13 Standard Template Library

The Standard Template Library (STL) is a library of containers, iterators and algorithms. I tend to think of the STL in terms of the container classes it provides, with algorithms and iterators necessary to make these containers useful. By segregating these roles, the STL becomes a powerful library—containers can store any kind of data and algorithms can use iterators to traverse the containers.

There are about half a dozen STL implementations. Since the STL relies so heavily on templates, these implementations tend to inline all of their method definitions. Thus, there are no precompiled STL libraries, and as an added bonus, you’re guaranteed to get the source code to your STL implementation. Hewlett-Packard and SGI produce freely redistributable STL implementations.

It is widely known that the STL can be implemented with complex C++ constructs and is a certain workout for any C++ compiler. The best policy for choosing an STL is to use a modern compiler such as GCC 2.95 or to use the STL that your vendor may have provided as part of their compiler.

Unfortunately, using the STL is pretty much an ‘all or nothing’ proposition. If it is not available on a particular system, there are no viable alternatives. There is a macro in the Autoconf macro archive (see section Autoconf macro archive) that can test for a working STL.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]

This document was generated by Ben Elliston on July 10, 2015 using texi2html 1.82.