Chapter 6: Classes

Don't hesitate to send in feedback: send an e-mail if you like the C++ Annotations; if you think that important material was omitted; if you find errors or typos in the text or the code examples; or if you just feel like e-mailing. Send your e-mail to Frank B. Brokken.

Please state the document version you're referring to, as found in the title (in this document: 6.5.0) and please state chapter and paragraph name or number you're referring to.

All received mail is processed conscientiously, and received suggestions for improvements will usually have been processed by the time a new version of the Annotations is released. Except for the incidental case I will normally not acknowledge the receipt of suggestions for improvements. Please don't interpret this as me not appreciating your efforts.

In this chapter classes are formally introduced. Two special member functions, the constructor and the destructor, are presented.

In steps we will construct a class Person, which could be used in a database application to store a person's name, address and phone number.

Let's start by creating the declaration of a class Person right away. The class declaration is normally contained in the header file of the class, e.g., person.h. A class declaration is generally not called a declaration, though. Rather, the common name for class declarations is class interface, to be distinguished from the definitions of the function members, called the class implementation. Thus, the interface of the class Person is given next:

    #include <string>

    class Person
    {
        std::string d_name;     // name of person
        std::string d_address;  // address field
        std::string d_phone;    // telephone number
        size_t    d_weight;   // the weight in kg.

        public:                 // interface functions
            void setName(std::string const &n);
            void setAddress(std::string const &a);
            void setPhone(std::string const &p);
            void setWeight(size_t weight);

            std::string const &name()    const;
            std::string const &address() const;
            std::string const &phone()   const;
            size_t weight()            const;
    };
It should be noted that this terminology is frequently loosely applied. Sometimes, class definition is used to indicate the class interface. While the class definition (so, the interface) contains the declarations of its members, the actual implementation of these members is also referred to as the definition of these members. As long as the concept of the class interface and the class implementation is well distinguished, it should be clear from the context what is meant by a `definition'.

The data fields in this class are d_name, d_address, d_phone and d_weight. All fields except d_weight are string objects. As the data fields are not given a specific access modifier, they are private, which means that they can only be accessed by the functions of the class Person. Alternatively, the label `private:' might have been used at the beginning of a private section of the class definition.

The data are manipulated by interface functions which take care of all communication with code outside of the class. Either to set the data fields to a given value (e.g., setName()) or to inspect the data (e.g., name()). Functions merely returning values stored inside the object, not allowing the caller to modify these internally stored values, are called accessor functions.

Note once again how similar the class is to the struct. The fundamental difference being that by default classes have private members, whereas structs have public members. Since the convention calls for the public members of a class to appear first, the keyword private is needed to switch back from public members to the (default) private situation.

A few remarks concerning style. Following Lakos (Lakos, J., 2001) Large-Scale C++ Software Design (Addison-Wesley). I suggest the following setup of class interfaces:

Style conventions usually take a long time to develop. There is nothing obligatory about them, however. I suggest that readers who have compelling reasons not to follow the above style conventions use their own. All others should adopt the above style conventions.

6.1: The constructor

A class in C++ may contain two special categories of member functions which are involved in the internal workings of the class. These member function categories are, on the one hand, the constructors and, on the other hand, the destructor. The destructor's primary task is to return memory allocated by an object to the common pool when an object goes `out of scope'. Allocation of memory is discussed in chapter 7, and destructors will therefore be discussed in depth in that chapter.

In this chapter the emphasis will be on the basic form of the class and on its constructors.

The constructor has by definition the same name as its class. The constructor does not specify a return value, not even void. E.g., for the class Person the constructor is Person::Person(). The C++ run-time system ensures that the constructor of a class, if defined, is called when a variable of the class, called an object, is defined (`created'). It is of course possible to define a class with no constructor at all. In that case the program will call a default constructor when a corresponding object is created. What actually happens in that case depends on the way the class has been defined. The actions of the default constructors are covered in section 6.4.1.

Objects may be defined locally or globally. However, in C++ most objects are defined locally. Globally defined objects are hardly ever required.

When an object is defined locally (in a function), the constructor is called every time the function is called. The object's constructor is then activated at the point where the object is defined (a subtlety here is that a variable may be defined implicitly as, e.g., a temporary variable in an expression).

When an object is defined as a static object (i.e., it is static variable) in a function, the constructor is called when the function in which the static variable is defined is called for the first time.

When an object is defined as a global object the constructor is called when the program starts. Note that in this case the constructor is called even before the function main() is started. This feature is illustrated in the following program:

    #include <iostream>
    using namespace std;

    class Demo
    {
        public:
            Demo();
    };

    Demo::Demo()
    {
        cout << "Demo constructor called\n";
    }

    Demo d;

    int main()
    {}

    /*
        Generated output:
    Demo constructor called
    */
The above listing shows how a class Demo is defined which consists of just one function: the constructor. The constructor performs but one action: a message is printed. The program contains one global object of the class Demo, and main() has an empty body. Nonetheless, the program produces some output.

Some important characteristics of constructors are:

6.1.1: A first application

As illustrated at the beginning of this chapter, the class Person contains three private string data members and an size_t d_weight data member. These data members can be manipulated by the interface functions.

Classes (should) operate as follows:

The set...() functions could be constructed as follows:
    #include "person.h"                 // given earlier

    // interface functions set...()
    void Person::setName(string const &name)
    {
        d_name = name;
    }

    void Person::setAddress(string const &address)
    {
        d_address = address;
    }

    void Person::setPhone(string const &phone)
    {
        d_phone = phone;
    }

    void Person::setWeight(size_t weight)
    {
        d_weight = weight;
    }

Next the accessor functions are defined. Note the occurence of the keyword const following the parameter lists of these functions: these member functions are called const member functions, indicating that they will not modify their object's data when they're called. Furthermore, notice that the return types of the member functions returning the values of the string data members are string const & types: the const here indicates that the caller of the member function cannot alter the returned value itself. The caller of the accessor member function could copy the returned value to a variable of its own, though, and that variable's value may then of course be modified ad lib. Const member functions are discussed in greater detail in section 6.2. The return value of the weight() member function, however, is a plain size_t, as this can be a simple copy of the value that's stored in the Person's weight member:

    #include "person.h"                 // given earlier

    // accessor functions ...()
    string const &Person::name() const
    {
        return d_name;
    }

    string const &Person::address() const
    {
       return d_address;
    }

    string const &Person::phone() const
    {
       return d_phone;
    }

    size_t Person::weight() const
    {
       return d_weight;
    }

The class definition of the Person class given earlier can still be used. The set...() and accessor functions merely implement the member functions declared in that class definition.

The following example shows the use of the class Person. An object is initialized and passed to a function printperson(), which prints the person's data. Note also the usage of the reference operator & in the argument list of the function printperson(). This way only a reference to an existing Person object is passed, rather than a whole object. The fact that printperson() does not modify its argument is evident from the fact that the parameter is declared const.

Alternatively, the function printperson() might have been defined as a public member function of the class Person, rather than a plain, objectless function.

    #include <iostream>
    #include "person.h"                 // given earlier

    void printperson(Person const &p)
    {
        cout << "Name    : " << p.name()     << endl <<
                "Address : " << p.address()  << endl <<
                "Phone   : " << p.phone()    << endl <<
                "Weight  : " << p.weight()   << endl;
    }

    int main()
    {
        Person p;

        p.setName("Linus Torvalds");
        p.setAddress("E-mail: Torvalds@cs.helsinki.fi");
        p.setPhone(" - not sure - ");
        p.setWeight(75);           // kg.

        printperson(p);
        return 0;
    }
/*
    Produced output:

Name    : Linus Torvalds
Address : E-mail: Torvalds@cs.helsinki.fi
Phone   :  - not sure -
Weight  : 75

*/

6.1.2: Constructors: with and without arguments

In the above declaration of the class Person the constructor has no arguments. C++ allows constructors to be defined with or without argument lists. The arguments are supplied when an object is created.

For the class Person a constructor expecting three strings and an size_t may be handy: these arguments then represent, respectively, the person's name, address, phone number and weight. Such a constructor is:

    Person::Person(string const &name, string const &address,
                    string const &phone, size_t weight)
    {
        d_name = name;
        d_address = address;
        d_phone = phone;
        d_weight = weight;
    }
The constructor must also be declared in the class interface:
    class Person
    {
        public:
            Person(std::string const &name, std::string const &address,
                   std::string const &phone, size_t weight);

            // rest of the class interface
    };
However, now that this constructor has been declared, the default constructor must be declared explicitly too, if we still want to be able to construct a plain Person object without any specific initial values for its data members.

Since C++ allows function overloading, such a declaration of a constructor can co-exist with a constructor without arguments. The class Person would thus have two constructors, and the relevant part of the class interface becomes:

    class Person
    {
        public:
            Person();
            Person(std::string const &name, std::string const &address,
                   std::string const &phone, size_t weight);

            // rest of the class interface
    };
In this case, the Person() constructor doesn't have to do much, as it doesn't have to initialize the string data members of the Person object: as these data members themselves are objects, they are already initialized to empty strings by default. However, there is also an size_t data member. That member is a variable of a basic type and basic type variabes are not initialized automatically. So, unless the value of the d_weight data member is explicitly initialized, it will be The 0-value might not be too bad, but normally we don't want a random value for our data members. So, the default constructor has a job to do: initializing the data members which are not initialized to sensible values automatically. Here is an implementation of the default constructor:
    Person::Person()
    {
        d_weight = 0;
    }
The use of a constructor with and without arguments (i.e., the default constructor) is illustrated in the following code fragment. The object a is initialized at its definition using the constructor with arguments, with the b object the default constructor is used:
    int main()
    {
        Person a("Karel", "Rietveldlaan 37", "542 6044", 70);
        Person b;

        return 0;
    }
In this example, the Person objects a and b are created when main() is started: they are local objects, living for as long as the main() function is active.

If Person objects must be contructed using other arguments, other constructors are required as well. It is also possible to define default parameter values. These default parameter values must be given in the class interface, e.g.,

    class Person
    {
        public:
            Person();
            Person(std::string const &name,
                   std::string const &address = "--unknown--",
                   std::string const &phone   = "--unknown--",
                   size_t weight = 0);

            // rest of the class interface
    };

Often, the constructors are implemented highly similar. This results from the fact that often the constructor's parameters are defined for convenience: a constructor not requiring a phone number but requiring a weight cannot be defined using default arguments, since only the last but one parameter in the constructor defining all four parameters is not required. This cannot be solved using default argument values, but only by defining another constructor, not requiring phone to be specified.

Although some languages (e.g., Java) allow constructors to call constructors, this is conceptually weird. It's weird because it makes a kludge out of the constructor concept. A constructor is meant to construct an object, not to construct itself while it hasn't been constructed yet.

In C++ the way to proceed is as follows: All constructors must initialize their reference data members, or the compiler will (rightfully) complain. This is one of the fundamental reasons why you can't call a constructor during a construction. Next, we have two options:

6.1.2.1: The order of construction The possibility to pass arguments to constructors allows us to monitor the construction of objects during a program's execution. This is shown in the next listing, using a class Test. The program listing below shows a class Test, a global Test object, and two local Test objects: in a function func() and in the main() function. The order of construction is as expected: first global, then main's first local object, then func()'s local object, and then, finally, main()'s second local object:

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

    class Test
    {
        public:
            Test(string const &name);   // constructor with an argument
    };

    Test::Test(string const &name)
    {
        cout << "Test object " << name << " created" << endl;
    }

    Test globaltest("global");

    void func()
    {
        Test functest("func");
    }

    int main()
    {
        Test first("main first");
        func();
        Test second("main second");
        return 0;
    }
/*
    Generated output:
Test object global created
Test object main first created
Test object func created
Test object main second created
*/

6.2: Const member functions and const objects

The keyword const is often used behind the parameter list of member functions. This keyword indicates that a member function does not alter the data members of its object, but will only inspect them. These member functions are called const member functions. Using the example of the class Person, we see that the accessor functions were declared const:
    class Person
    {
        public:
            std::string const &name() const;
            std::string const &address() const;
            std::string const &phone() const;
    };
This fragment illustrates that the keyword const appears behind the functions' argument lists. Note that in this situation the rule of thumb given in section 3.1.3 applies as well: whichever appears before the keyword const, may not be altered and doesn't alter (its own) data.

The const specification must be repeated in the definitions of member functions:

    string const &Person::name() const
    {
        return d_name;
    }
A member function which is declared and defined as const may not alter any data fields of its class. In other words, a statement like
    d_name = 0;
in the above const function name() would result in a compilation error.

Const member functions exist because C++ allows const objects to be created, or (used more often) references to const objects to be passed to functions. For such objects only member functions which do not modify it, i.e., the const member functions, may be called. The only exception to this rule are the constructors and destructor: these are called `automatically'. The possibility of calling constructors or destructors is comparable to the definition of a variable int const max = 10. In situations like these, no assignment but rather an initialization takes place at creation-time. Analogously, the constructor can initialize its object when the const variable is created, but subsequent assignments cannot take place.

The following example shows the definition of a const object of the class Person. When the object is created the data fields are initialized by the constructor:

    Person const me("Karel", "karel@icce.rug.nl", "542 6044");

Following this definition it would be illegal to try to redefine the name, address or phone number for the object me: a statement as

    me.setName("Lerak");
would not be accepted by the compiler. Once more, look at the position of the const keyword in the variable definition: const, following Person and preceding me associates to the left: the Person object in general must remain unaltered. Hence, if multiple objects were defined here, both would be constant Person objects, as in:
    Person const        // all constant Person objects
        kk("Karel", "karel@icce.rug.nl", "542 6044"),
        fbb("Frank", "f.b.brokken@rug.nl", "363 9281");

Member functions which do not modify their object should be defined as const member functions. This subsequently allows the use of these functions with const objects or with const references. As a rule of thumb it is stated here that member functions should always be given the const attribute, unless they actually modify the object's data.

Earlier, in section 2.5.11 the concept of function overloading was introduced. There it noted that member functions may be overloaded merely by their const attribute. In those cases, the compiler will use the member function matching most closely the const-qualification of the object:

An example showing the selection of (non) const member functions is given in the following example:
    #include <iostream>
    using namespace std;

    class X
    {
        public:
            X();
            void member();
            void member() const;
    };

    X::X()
    {}
    void X::member()
    {
        cout << "non const member\n";
    }
    void X::member() const
    {
        cout << "const member\n";
    }

    int main()
    {
        X const constObject;
        X       nonConstObject;

        constObject.member();
        nonConstObject.member();
    }
    /*
            Generated output:

        const member
        non const member
    */
Overloading member functions by their const attribute commonly occurs in the context of operator overloading. See chapter 9, in particular section 9.1 for details.

6.2.1: Anonymous objects

Situations exists where objects are used because they offer a certain functionality. They only exist because of the functionality they offer, and nothing in the objects themselves is ever changed. This situation resembles the well-known situation in the C programming language where a function pointer is passed to another function, to allow run-time configuration of the behavior of the latter function.

For example, the class Print may offer a facility to print a string, prefixing it with a configurable prefix, and affixing a configurable affix to it. Such a class could be given the following prototype:

    class Print
    {
        public:
            printout(std::string const &prefix, std::string const &text,
                     std::string const &affix) const;
    };
An interface like this would allow us to do things like:
    Print print;
    for (int idx = 0; idx < argc; ++idx)
        print.printout("arg: ", argv[idx], "\n");
This would work well, but can greatly be improved if we could pass printout's invariant arguments to Print's constructors: this way we would not only simplify printout's prototype (only one argument would need to be passed rather than three, allowing us to make faster calls to printout) but we could also capture the above code in a function expecting a Print object:
    void printText(Print const &print, int argc, char *argv[])
    {
        for (int idx = 0; idx < argc; ++idx)
            print.printout(argv[idx]);
    }
Now we have a fairly generic piece of code, at least as far as Print is concerned. If we would provide Print's interface with the following constructors we would be able to configure our output stream as well:
    Print(char const *prefix, char const *affix);
    Print(ostream &out, char const *prefix, char const *affix);
Now printText could be used as follows:
    Print p1("arg: ", "\n");            // prints to cout
    Print p2(cerr, "err: --", "--\n");  // prints to cerr

    printText(p1, argc, argv);          // prints to cout
    printText(p2, argc, argv);          // prints to cerr
However, when looking closely at this example, it should be clear that both p1 and p2 are only used inside the printText function. Furthermore, as we can see from printText's prototype, printText won't modify the internal data of the Print object it is using.

In situations like these it is not necessary to define objects before they are used. Instead anonymous objects should be used. Using anonymous objects is indicated when:

Anonymous objects are defined by calling a constructor without providing a name for the constructed object. In the above example anonymous objects can be used as follows:

    printText(Print("arg: ", "\n"), argc, argv);          // prints to cout
    printText(Print(cerr, "err: --", "--\n"), argc, argv);// prints to cerr
In this situation the Print objects are constructed and immediately passed as first arguments to the printText functions, where they are accessible as the function's print parameter. While the printText function is executing they can be used, but once the function has completed, the Print objects are no longer accessible.

Anonymous objects cease to exist when the function for which they were created has terminated. In this respect they differ from ordinary local variables whose lifetimes end by the time the function block in which they were defined is closed.

6.2.1.1: Subtleties with anonymous objects As discussed, anonymous objects can be used to initialize function parameters that are const references to objects. These objects are created just before such a function is called, and are destroyed once the function has terminated. This use of anonymous objects to initialize function parameters is often seen, but C++'s grammar allows us to use anonymous objects in other situations as well. Consider the following snippet of code:

    int main()
    {
        // initial statements
        Print("hello", "world");
        // later statements
    }
In this example the anonymous Print object is constructed, and is immediately destroyed after its construction. So, following the `initial statements' our Print object is constructed, then it is destroyed again, followed by the execution of the `later statements'. This is remarkable as it shows that the standard lifetime rules do not apply to anonymous objects. Their lifetime is limited to the statement, rather than to the end of the block in which they are defined.

Of course one might wonder why a plain anonymous object could ever be considered useful. One might think of at least one situation, though. Assume we want to put markers in our code producing some output when the program's execution reaches a certain point. An object's constructor could be implemented so as to provide that marker-functionality, thus allowing us to put markers in our code by defining anonymous, rather than named objects.

However, C++'s grammar contains another remarkable characteristic. Consider the next example:

    int main(int argc, char *argv[])
    {
        Print p("", "");                    // 1
        printText(Print(p), argc, argv);    // 2
    }
In this example a non-anonymous object p is constrcted in statement 1, which object is then used in statement 2 to initialize an anonymous object which, in turn, is then used to initialize printText's const reference parameter. This use of an existing object to initialize another object is common practice, and is based on the existence of a so-called copy constructor. A copy constructor creates an object (as it is a constructor), using an existing object's characteristics to initialize the new object's data. Copy constructors are discussed in depth in chapter 7, but presently merely the concept of a copy constructor is used.

In the last example a copy constructor was used to initialize an anonymous object, which was then used to initialize a parameter of a function. However, when we try to apply the same trick (i.e., using an existing object to initialize an anonymous object) to a plain statement, the compiler generates an error: the object p can't be redefined (in statement 3, below):

    int main(int argc, char *argv[])
    {
        Print p("", "");                    // 1
        printText(Print(p), argc, argv);    // 2
        Print(p);                           // 3 error!
    }
So, using an existing object to initialize an anonymous object that is used as function argument is ok, but an existing object can't be used to initialize an anonymous object in a plain statement?

The answer to this apparent contradiction is actually found in the compiler's error message itself. At statement 3 the compiler states something like:

    error: redeclaration of 'Print p'
which solves the problem, by realizing that within a compound statement objects and variables may be defined as well. Inside a compound statement, a type name followed by a variable name is the grammatical form of a variable definition. Parentheses can be used to break priorities, but if there are no priorities to break, they have no effect, and are simply ignored by the compiler. In statement 3 the parentheses allowed us to get rid of the blank that's required between a type name and the variable name, but to the compiler we wrote
        Print (p);
which is, since the parentheses are superfluous, equal to
        Print p;
thus producing p's redeclaration.

As a further example: when we define a variable using a basic type (e.g., double) using superfluous parentheses the compiler will quietly remove these parentheses for us:

    double ((((a))));       // weird, but ok.

To summarize our findings about anonymous variables:

6.3: The keyword `inline'

Let us take another look at the implementation of the function Person::name():
    std::string const &Person::name() const
    {
        return d_name;
    }
This function is used to retrieve the name field of an object of the class Person. In a code fragment like:
    Person frank("Frank", "Oostumerweg 17", "403 2223");

    cout << frank.name();
the following actions take place:

Especially the first part of these actions results in some time loss, since an extra function call is necessary to retrieve the value of the name field. Sometimes a faster procedure may be desirable, in which the name field becomes immediately available, without ever actually calling a function name(). This can be realized using inline functions.

6.3.1: Defining members inline

Inline functions may be implemented in the class interface itself. For the class Person this results in the following implementation of name():
    class Person
    {
        public:
            std::string const &name() const
            {
                return d_name;
            }
    };
Note that the inline code of the function name() now literally occurs inline in the interface of the class Person. The keyword const occurs after the function declaration, and before the code block.

Although members can be defined inside the class interface itself, it should be considered bad practice because of the following considerations:

Because of the above considerations inline members should not be defined within the class interface. Rather, they should be defined below the class interface. The name() member of the Person class is therefore preferably defined as follows:
    class Person
    {
        public:
            std::string const &name() const;
    };

    inline std::string const &Person::name() const
    {
        return d_name;
    }
This version of the Person class clearly shows that: Defining members inline has the following effect: Whenever an inline function is called in a program statement, the compiler may insert the function's body at the location of the function call. The function itself may never actually be called. Consequently, the function call is prevented, but the function's body appears as often in the final program as the inline function is actually called.

This construction, where the function code itself is inserted rather than a call to the function, is called an inline function. Note that using inline functions may result in multiple occurrences of the code of those functions in a program: one copy for each invocation of the inline function. This is probably ok if the function is a small one, and needs to be executed fast. It's not so desirable if the code of the function is extensive. The compiler knows this too, and considers the use of inline functions a request rather than a command: if the compiler considers the function too long, it will not grant the request, but will, instead, treat the function as a normal function. As a rule of thumb: members should only be defined inline if they are small (containing a single, small statement) and if it is highly unlikely that their definition will ever change.

6.3.2: When to use inline functions

When should inline functions be used, and when not? There are some rules of thumb which may be followed: All inline functions have one disadvantage: the actual code is inserted by the compiler and must therefore be known compile-time. Therefore, as mentioned earlier, an inline function can never be located in a run-time library. Practically this means that an inline function is placed near the interface of a class, usually in the same header file. The result is a header file which not only shows the declaration of a class, but also part of its implementation, thus blurring the distinction between interface and implementation.

Finally, note once again that the keyword inline is not really a command to the compiler. Rather, it is a request the compiler may or may not grant.

6.4: Objects inside objects: composition

Often objects are used as data members in class definitions. This is called composition.

For example, the class Person holds information about the name, address and phone number. This information is stored in string data members, which are themselves objects: composition.

Composition is not extraordinary or C++ specific: in C a struct or union field is commonly used in other compound types.

The initialization of composed objects deserves some special attention: the topics of the coming sections.

6.4.1: Composition and const objects: const member initializers

Composition of objects has an important consequence for the constructor functions of the `composed' (embedded) object. Unless explicitly instructed otherwise, the compiler generates code to call the default constructors of all composed classes in the constructor of the composing class.

Often it is desirable to initialize a composed object from a specific constructor of the composing class. This is illustrated below for the class Person. In this fragment it assumed that a constructor for a Person should be defined expecting four arguments: the name, address and phone number plus the person's weight:

    Person::Person(char const *name, char const *address,
                    char const *phone, size_t weight)
    :
        d_name(name),
        d_address(address),
        d_phone(phone),
        d_weight(weight)
    {}
Following the argument list of the constructor Person::Person(), the constructors of the string data members are explicitly called, e.g., name(mn). The initialization takes place before the code block of Person::Person() (now empty) is executed. This construction, where member initialization takes place before the code block itself is executed is called member initialization. Member initialization can be made explicit in the member initializer list, that may appear after the parameter list, between a colon (announcing the start of the member initializer list) and the opening curly brace of the code block of the constructor.

Member initialization always occurs when objects are composed in classes: if no constructors are mentioned in the member initializer list the default constructors of the objects are called. Note that this only holds true for objects. Data members of primitive data types are not initialized automatically.

Member initialization can, however, also be used for primitive data members, like int and double. The above example shows the initialization of the data member d_weight from the parameter weight. Note that with member initializers the data member could even have the same name as the constructor parameter (although this is deprecated): with member initialization there is no ambiguity and the first (left) identifier in, e.g., weight(weight) is interpreted as the data member to be initialized, whereas the identifier between parentheses is interpreted as the parameter.

When a class has multiple composed data members, all members can be initialized using a `member initializer list': this list consists of the constructors of all composed objects, separated by commas. The order in which the objects are initialized is defined by the order in which the members are defined in the class interface. If the order of the initialization in the constructor differs from the order in the class interface, the compiler complains, and reorders the initialization so as to match the order of the class interface.

Member initializers should be used as often as possible: it can be downright necessary to use them, and not using member initializers can result in inefficient code: with objects always at least the default constructor is called. So, in the following example, first the string members are initialized to empty strings, whereafter these values are immediately redefined to their intended values. Of course, the immediate initialization to the intended values would have been more efficent.

    Person::Person(char const *name, char const *address,
                    char const *phone, size_t weight)
    {
        d_name = name;
        d_address = address;
        d_phone = phone;
        d_weight = weight;
    }
This method is not only inefficient, but even more: it may not work when the composed object is declared as a const object. A data field like birthday is a good candidate for being const, since a person's birthday usually doesn't change too much.

This means that when the definition of a Person is altered so as to contain a string const birthday member, the implementation of the constructor Person::Person() in which also the birthday must be initialized, a member initializer must be used for birthday. Direct assignment of the birthday would be illegal, since birthday is a const data member. The next example illustrates the const data member initialization:

    Person::Person(char const *name, char const *address,
                    char const *phone, char const *birthday,
                    size_t weight)
    :
        d_name(name),
        d_address(address),
        d_phone(phone),
        d_birthday(birthday),       // assume: string const d_birthday
        d_weight(weight)
    {}
Concluding, the rule of thumb is the following: when composition of objects is used, the member initializer method is preferred to explicit initialization of composed objects. This not only results in more efficient code, but it also allows composed objects to be declared as const objects.

6.4.2: Composition and reference objects: reference member initializers

Apart from using member initializers to initialize composed objects (be they const objects or not), there is another situation where member initializers must be used. Consider the following situation.

A program uses an object of the class Configfile, defined in main() to access the information in a configuration file. The configuration file contains parameters of the program which may be set by changing the values in the configuration file, rather than by supplying command line arguments.

Assume that another object that is used in the function main() is an object of the class Process, doing `all the work'. What possibilities do we have to tell the object of the class Process that an object of the class Configfile exists?

However, the following construction will not result in the initialization of the Configfile &d_conf reference data member:
    Process::Process(Configfile &conf)
    {
        d_conf = conf;        // wrong: no assignment
    }
The statement d_conf = conf fails, because the compiler won't see this as an initialization, but considers this an assignment of one Configfile object (i.e., conf), to another (d_conf). It does so, because that's the normal interpretation: an assignment to a reference variable is actually an assignment to the variable the reference variable refers to. But to what variable does d_conf refer? To no variable, since we haven't initialized d_conf. After all, the whole purpose of the statement d_conf = conf was to initialize d_conf....

So, how do we proceed when d_conf must be initialized? In this situation we once again use the member initializer syntax. The following example shows the correct way to initialize d_conf:

    Process::Process(Configfile &conf)
    :
        d_conf(conf)      // initializing reference member
    {}
Note that this syntax must be used in all cases where reference data members are used. If d_ir would be an int reference data member, a construction like
    Process::Process(int &ir)
    :
        d_ir(ir)
    {}
would have been called for.

6.5: The keyword `mutable'

Earlier, in section 6.2, the concepts of const member functions and const objects were introduced.

C++, however, allows the construction of objects which are, in a sense, neither const objects, nor non-const objects. Data members which are defined using the keyword mutable, can be modified by const member functions.

An example of a situation where mutable might come in handy is where a const object needs to register the number of times it was used. The following example illustrates this situation:

#include <string>
#include <iostream>
#include <memory>


class Mutable
{
    std::string d_name;
    mutable int d_count;                // uses mutable keyword

    public:
        Mutable(std::string const &name)
        :
            d_name(name),
            d_count(0)
        {}

        void called() const
        {
            std::cout << "Calling " << d_name <<
                                    " (attempt " << ++d_count << ")\n";
        }
};


int main()
{
    Mutable const x("Constant mutable object");

    for (int idx = 0; idx < 4; idx++)
        x.called();                     // modify data of const object
}

/*
    Generated output:

    Calling Constant mutable object (attempt 1)
    Calling Constant mutable object (attempt 2)
    Calling Constant mutable object (attempt 3)
    Calling Constant mutable object (attempt 4)
*/

The keyword mutable may also be useful in classes implementing, e.g., reference counting. Consider a class implementing reference counting for textstrings. The object doing the reference counting might be a const object, but the class may define a copy constructor. Since const objects can't be modified, how would the copy constructor be able to increment the reference count? Here the mutable keyword may profitably be used, as it can be incremented and decremented, even though its object is a const object.

The advantage of having a mutable keyword is that, in the end, the programmer decides which data members can be modified and which data members can't. But that might as well be a disadvantage: having the keyword mutable around prevents us from making rigid assumptions about the stability of const objects. Depending on the context, that may or may not be a problem. In practice, mutable tends to be useful only for internal bookkeeping purposes: accessors returning values of mutable data members might return puzzling results to clients using these accessors with const objects. In those situations, the nature of the returned value should clearly be documented. As a rule of thumb: do not use mutable unless there is a very clear reason to divert from this rule.

6.6: Header file organization

In section 2.5.9 the requirements for header files when a C++ program also uses C functions were discussed.

When classes are used, there are more requirements for the organization of header files. In this section these requirements are covered.

First, the source files. With the exception of the occasional classless function, source files should contain the code of member functions of classes. With source files there are basically two approaches:

The first alternative has the advantage of economy for the compiler: it only needs to read the header files that are necessary for a particular source file. It has the disadvantage that the program developer must include multiple header files again and again in sourcefiles: it both takes time to type the include-directives and to think about the header files which are needed in a particular source file.

The second alternative has the advantage of economy for the program developer: the header file of the class accumulates header files, so it tends to become more and more generally useful. It has the disadvantage that the compiler frequently has to read header files which aren't actually used by the function defined in the source file.

With computers running faster and faster we think the second alternative is to be preferred over the first alternative. So, as a starting point we suggest that source files of a particular class MyClass are organized according to the following example:

    #include <myclass.h>

    int MyClass::aMemberFunction()
    {}
There is only one include-directive. Note that the directive refers to a header file in a directory mentioned in the INCLUDE-file environment variable. Local header files (using #include "myclass.h") could be used too, but that tends to complicate the organization of the class header file itself somewhat.

If name collisions with existing header files might occur it pays off to have a subdirectory of one of the directories mentioned in the INCLUDE environment variable (e.g., /usr/local/include/myheaders/).

If a class MyClass is developed there, create a subdirectory (or subdirectory link) myheaders of one of the standard INCLUDE directories to contain all header files of all classes that are developed as part of the project. The include-directives will then be similar to #include <myheaders/myclass.h>, and name collisions with other header files are avoided.

The organization of the header file itself requires some attention. Consider the following example, in which two classes File and String are used.

Assume the File class has a member gets(String &destination), while the class String has a member function getLine(File &file). The (partial) header file for the class String is then:

    #ifndef _String_h_
    #define _String_h_

    #include <project/file.h>   // to know about a File

    class String
    {
        public:
            void getLine(File &file);
    };
    #endif
However, a similar setup is required for the class File:
    #ifndef _File_h_
    #define _File_h_

    #include <project/string.h>   // to know about a String

    class File
    {
        public:
            void gets(String &string);
    };
    #endif
Now we have created a problem. The compiler, trying to compile the source file of the function File::gets() proceeds as follows: The solution for this problem is to use a forward class reference before the class interface, and to include the corresponding class header file after the class interface. So we get:
    #ifndef _String_h_
    #define _String_h_

    class File;                 // forward reference

    class String
    {
        public:
            void getLine(File &file);
    };

    #include <project/file.h>   // to know about a File

    #endif
A similar setup is required for the class File:
    #ifndef _File_h_
    #define _File_h_

    class String;               // forward reference

    class File
    {
        public:
            void gets(String &string);
    };

    #include <project/string.h>   // to know about a String

    #endif
This works well in all situations where either references or pointers to another classes are involved and with (non-inline) member functions having class-type return values or parameters.

Note that this setup doesn't work with composition, nor with inline member functions. Assume the class File has a composed data member of the class String. In that case, the class interface of the class File must include the header file of the class String before the class interface itself, because otherwise the compiler can't tell how big a File object will be, as it doesn't know the size of a String object once the interface of the File class is completed.

In cases where classes contain composed objects (or are derived from other classes, see chapter 13) the header files of the classes of the composed objects must have been read before the class interface itself. In such a case the class File might be defined as follows:

    #ifndef _File_h_
    #define _File_h_

    #include <project/string.h>     // to know about a String

    class File
    {
        String d_line;              // composition !

        public:
            void gets(String &string);
    };
    #endif
Note that the class String can't have a File object as a composed member: such a situation would result again in an undefined class while compiling the sources of these classes.

All remaining header files (appearing below the class interface itself) are required only because they are used by the class's source files.

This approach allows us to introduce yet another refinement:

6.6.1: Using namespaces in header files

When entities from namespaces are used in header files, in general using directives should not be used in these header files if they are to be used as general header files declaring classes or other entities from a library. When the using directive is used in a header file then users of such a header file are forced to accept and use the declarations in all code that includes the particular header file.

For example, if in a namespace special an object Inserter cout is declared, then special::cout is of course a different object than std::cout. Now, if a class Flaw is constructed, in which the constructor expects a reference to a special::Inserter, then the class should be constructed as follows:

    class special::Inserter;

    class Flaw
    {
    public:
        Flaw(special::Inserter &ins);
    };
Now the person designing the class Flaw may be in a lazy mood, and might get bored by continuously having to prefix special:: before every entity from that namespace. So, the following construction is used:
    using namespace special;

    class Inserter;

    class Flaw
    {
    public:
        Flaw(Inserter &ins);
    };
This works fine, up to the point where somebody wants to include flaw.h in other source files: because of the using directive, this latter person is now by implication also using namespace special, which could produce unwanted or unexpected effects:
    #include <flaw.h>
    #include <iostream>

    using std::cout;

    int main()
    {
        cout << "starting" << endl;     // doesn't compile
    }
The compiler is confronted with two interpretations for cout: first, because of the using directive in the flaw.h header file, it considers cout a special::Extractor, then, because of the using directive in the user program, it considers cout a std::ostream. As compilers do, when confronted with an ambiguity, an error is reported.

As a rule of thumb, header files intented to be generally used should not contain using declarations. This rule does not hold true for header files which are included only by the sources of a class: here the programmer is free to apply as many using declarations as desired, as these directives never reach other sources.