Chapter 5: Classes

We're always interested in getting feedback. E-mail us if you like this guide, if you think that important material is omitted, if you encounter errors in the code examples or in the documentation, if you find any typos, or generally just if you feel like e-mailing. Send your email to Frank Brokken.

Please state the document version you're referring to, as found in the title (in this document: 4.4.2).

In this chapter classes are the topic of discussion. Two special member functions, the constructor and the destructor, are introduced.

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

Let's start off by introducing 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. The 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:


    class Person
    {
        public:                 // interface functions
            void setname(char const *n);
            void setaddress(char const *a);
            void setphone(char const *p);

            char const *getname(void);
            char const *getaddress(void);
            char const *getphone(void);

        private:                // data fields
            char *name;         // name of person
            char *address;      // address field
            char *phone;        // telephone number
    };

The data fields in this class are name, address and phone. The fields are char *s which point to allocated memory. The data are private, which means that they can only be accessed by the functions of the class Person.

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., getname()).

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.

5.1: Constructors and destructors

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 basic forms and functions of these two categories are discussed next.

5.1.1: The constructor

The constructor member function has by definition the same name as the corresponding class. The constructor has no return value specification, not even void. E.g., for the class Person the constructor is Person::Person(). The C++ run-time system makes sure that the constructor of a class, if defined, is called when an object of the class is created. It is of course possible to define a class which has no constructor at all; in that case the run-time system either calls no function or it calls a dummy constructor (i.e., a constructor which performs no actions) when a corresponding object is created. The actual generated code of course depends on the compiler (A compiler-supplied constructor in a class which contains composed objects (see section 5.5) will `automatically' call the member initializers, and therefore does perform some actions. We postpone the discussion of such constructors to 5.5.1.).

Objects may be defined at a local (function) level, or at a global level (in which its status is comparable to a global variable.

When an object is a local (non-static) variable of a function, the constructor is called every time the function is called at the point where the variable 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 a static variable, the constructor is called when the function in which the static variable is defined is called for the first time.

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


    #include <iostream>

    // a class Test with a constructor function
    class Test
    {
        public:                 // 'public' function:
            Test();             // the constructor
    };

    Test::Test()                // here is the
    {                           // definition
        cout << "constructor of class Test called\n";
    }

    // and here is the test program:
    Test                
        g;                      // global object

    void func()
    {
        Test                    // local object
            l;                  // in function func()

        cout << "here's function func()" << endl;
    }

    int main()
    {
        Test                    // local object
            x;                  // in function main()

        cout << "main() function" << endl;
        func();
        return (0);
    }

The listing shows how a class Test is defined which consists of only one function: the constructor. The constructor performs only one action; a message is printed. The program contains three objects of the class Test: one global object, one local object in main() and one local object in func().

Concerning the definition of a constructor we have the following remarks:

The constructor of the three objects of the class Test in the above listing are called in the following order:

As expected, the program yields therefore the following output (the text in parentheses is added for illustration purposes):


    constructor of class Test called        (global object g)
    constructor of class Test called        (object x in main())
    main() function
    constructor of class Test called        (object l in func())
    here's function func()

5.1.2: The destructor

The second special member function is the destructor. This function is the opposite of the constructor in the sense that it is invoked when an object ceases to exist. For objects which are local non-static variables, the destructor is called when the block in which the object is defined is left: the destructors of objects that are defined in nested blocks of functions are therefore usually called before the function itself terminates. The destructors of objects that are defined somewhere in the outer block of a function are called just before the function returns (terminates). For static or global variables the destructor is called before the program terminates.

However, when a program is interrupted using an exit() call, the destructors are called only for global objects which exist at that time. Destructors of objects defined locally within functions are not called when a program is forcefully terminated using exit().

When defining a destructor for a given class the following rules apply:

The destructor for the class Test from the previous section could be declared as follows:


    class Test
    {
        public:
            Test();                    // constructor
            ~Test();                   // destructor
            // any other members
    };

The position of the constructor(s) and destructor in the class definition is dictated by convention: First the constructors are declared, then the destructor, and only then any other members follow.

5.1.3: A first application

One of the applications of constructors and destructors is the management of memory allocation. This is illustrated using the class Person.

As illustrated at the beginning of this chapter, the class Person contains three private pointers, all char *s. These data members are manipulated by the interface functions. The internal workings of the class are as follows: when a name, address or phone number of a Person is defined, memory is allocated to store these data. An obvious setup is described below:

The set...() functions are illustrated below. Strings are duplicated in this example by an imaginary function xstrdup(), which would duplicate a string or terminate the program when the memory pool is exhausted ( As a word to the initiated reader it is noted here that many other ways to handle the memory allocation are possible here: As discussed in section 6, new could be used, together with set_new_handler(), or exceptions could be used to catch any failing memory allocation. However, since we haven't covered that subject yet, and since these annotations start from C, we used the tried and true method of a `protected allocation function' xstrdup() here for didactical reasons.).


    // interface functions set...()
    void Person::setname(char const *n)
    {
        free(name);
        name = xstrdup(n);
    }

    void Person::setaddress(char const *a)
    {
        free(address);
        address = xstrdup(a);
    }

    void Person::setphone(char const *p)
    {
        free(phone);
        phone = xstrdup(p);
    }

Note that the statements free(...) in the above listing are executed unconditionally. This never leads to incorrect actions: when a name, address or phone number is defined, the corresponding pointers point to previously allocated memory which should be freed. When the data are not (yet) defined, then the corresponding pointer is a 0-pointer; and free(0) performs no action (Actually, free(0) should perform no action. However, later on we'll introduce the operators new and delete. With the delete operator delete 0 is formally ignored.).

Furthermore it should be noted that this code example uses the standard C function free() which should be familiar to most C programmers. The delete statement, which has more `C++ flavor', will be discussed later.

The interface functions get...() are defined now. Note the occurence of the keyword const following the parameter lists of the functions: the member functions are const member functions, indicating that they will not modify their object when they're called. The matter of const member functions is postponed to section 5.2, where it will be discussed in greater detail.


    // interface functions get...()
    char const *Person::getname() const
    {
        return (name);
    }

    char const *Person::getaddress() const
    {
       return (address);
    }

    char const *Person::getphone() const
    {
       return (phone);
    }

The destructor, constructor and the class definition are given below.


    // class definition
    class Person
    {
        public:
            Person();          // constructor
            ~Person();         // destructor

            // functions to set fields
            void setname(char const *n);
            void setaddress(char const *a);
            void setphone(char const *p);

            // functions to inspect fields
            char const *getname() const;
            char const *getaddress() const;
            char const *getphone() const;

        private:
            char *name;             // name of person
            char *address;          // address field
            char *phone;            // telephone number
    };

    // constructor
    Person::Person()
    {
        name = 0;
        address = 0;
        phone = 0;
    }

    // destructor
    Person::~Person()
    {
        free(name);         
        free(address);
        free(phone);
    }

To demonstrate the usage of the class Person, a code example follows next. An object is initialized and passed to a function printperson(), which prints the contained data. Note also the usage of the reference operator & in the argument list of the function printperson(). This way only a reference to a 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 argument is declared const. Also note that the example doesn't show where the destructor is called; this action occurs implicitly when the below function main() terminates and hence when its local variable p ceases to exist.

It should also be noted that the function printperson() could be defined as a public member function of the class Person.


    #include <iostream>

    void printperson(Person const &p)
    {
        cout << "Name    : " << p.getname() << endl
             << "Address : " << p.getaddress() << endl
             << "Phone   : " << p.getphone() << endl;
    }

    int main()
    {
        Person
            p;

        p.setname("Linus Torvalds");
        p.setaddress("E-mail: Torvalds@cs.helsinki.fi");
        p.setphone(" - not sure - ");

        printperson(p);
        return (0);
    }

When printperson() receives a fully defined Person object (i.e., containing a name, address and phone number), the data are correctly printed. However, when a Person object is only partially filled, e.g. with only a name, printperson() passes 0-pointers to cout. This unesthetic feature can be remedied with a little more code:


    void printperson(Person const &p)
    {
        if (p.getname())
            cout << "Name   : " << p.getname() << "\n";
        if (p.getaddress())
            cout << "Address : " << p.getaddress() << "\n";
        if (p.getphone())
            cout << "Phone  : " << p.getphone() << "\n";
    }

Alternatively, the constructor Person::Person() might initialize the members to `printable defaults', like " ** undefined ** ".

5.1.4: Constructors with arguments

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

For the class Person a constructor may be handy which expects three strings: the name, address and phone number. Such a constructor is shown below:


    Person::Person(char const *n, char const *a, char const *p)
    {
        name = xstrdup(n);
        address = xstrdup(a);
        phone = xstrdup(p);
    }

The constructor must be included in the class declaration, as illustrated here:


    class Person
    {
        public:
            Person::Person(char const *n,
                char const *a, char const *p);
            .
            .
            .
    };

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.

The usage of a constructor with arguments is illustrated in the following code fragment. The object a is initialized at its definition:


    int main()
    {
        Person
            a("Karel", "Rietveldlaan 37", "542 6044"),
            b;

        return (0);
    }

In this example, the Person objects a and b are created when main() is started. For the object a the constructor with arguments is selected by the compiler. For the object b the default constructor (without arguments) is used.

5.1.4.1: The order of construction

The possibility to pass arguments to constructors offers us the chance to monitor at which exact moment in a program's execution an object is created or destroyed. This is shown in the next listing, using a class Test:


    class Test
    {
        public:
            // constructors:
            Test();                    // argument-free
            Test(char const *name);    // with a name argument
            // destructor:
            ~Test();

        private:
            // data:
            char *n;                    // name field
    };

    Test::Test()
    {
        n = xstrdup("without name");
        printf("Test object without name created\n");
    }

    Test::Test(char const *name)
    {
        n = xstrdup(name);
        cout << "Test object " << name << " created" << endl;
    }

    Test::~Test()
    {
        cout << "Test object " << n << " destroyed" << endl;
        free(n);
    }

By defining objects of the class Test with specific names, the construction and destruction of these objects can be monitored:


    Test
        globaltest("global");

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

    int main()
    {
        Test
            maintest("main");

        func();
        return (0);
    }

This test program thus leads to the following (and expected) output:


    Test object global created
    Test object main created
    Test object func created
    Test object func destroyed
    Test object main destroyed
    Test object global destroyed

5.2: Const member functions and const objects

The keyword const is often seen in the declarations of member functions following the argument list. This keyword is used to indicate that a member function does not alter the data fields of its object, but only inspects them. Using the example of the class Person, the get...() functions should be declared const:


    class Person
    {
        public:
            .
            .
            // functions to inspect fields
            char const *getname(void) const;
            char const *getaddress(void) const;
            char const *getphone(void) const;

        private:
            .
            .
    };

As is illustrated in this fragment, the keyword const occurs following the argument list of functions. Note that in this situation the rule of thumb given in section 3.1.3 applies once again: whichever appears before the keyword const, may not be altered and doesn't alter (its own) data.

The same specification must be repeated in the definition of member functions themselves:


    char const *Person::getname() const
    {
        return (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


    name = 0;

in the above const function getname() would result in a compilation error.

The const member functions exist because C++ allows const objects to be created, or references to const objects to be passed on 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 variable is created, but subsequent assignments cannot take place.

The following example shows how a const object of the class Person can be defined. 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", "frank@icce.rug.nl", "403 2223");

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.

5.3: The operators new and delete

The C++ language defines two operators which are specific for the allocation and deallocation of memory. These operators are new and delete.

The most basic example of the use of these operators is given below. An int pointer variable is used to point to memory which is allocated by the operator new. This memory is later released by the operator delete.


    int
        *ip;

    ip = new int;
    // any other statements
    delete ip;
 
Note that new and delete are operators and therefore do not require parentheses, which are required for functions like malloc() and free(). The operator delete returns void, the operator new returns a pointer to the kind of memory that's asked for by its argument (e.g., a pointer to an int in the above example).

5.3.1: Allocating and deallocating arrays

When the operator new is used to allocate an array, the size of the variable is placed between square brackets following the type:


    int
        *intarr;

    intarr = new int [20];    // allocates 20 ints

The syntactical rule for the operator new is that this operator must be followed by a type, optionally followed by a number in square brackets. The type and number specification lead to an expression which is used by the compiler to deduce its size; in C an expression like sizeof(int[20]) might be used.

An array is deallocated by using the operator delete:


    delete [] intarr;

In this statement the array operators [] indicate that an array is being deallocated. The rule of thumb here is: whenever new is followed by [], delete should be followed by it too.

What happens if delete rather than delete [] is used? Consider the following situation: a class X is defined having a destructor telling us that it's called. In a main() function an array of two X objects is allocated by new, to be deleted by delete []. Next, the same actions are repeated, albeit that the delete operator is called without []:


#include <iostream>

class X
{ 
    public:
        ~X();
};

X::~X()
{
    cout << "X destructor called" << endl;
}

int main()
{
    X 
      *a;

    a = new X[2];
             
    cout << "Destruction with []'s" << endl;

    delete [] a;
    
    a = new X[2];
    
    cout << "Destruction without []'s" << endl;
                             
    delete a;

    return (0);
}

Here's the generated output:


    Destruction with []'s
    X destructor called
    X destructor called
    Destruction without [] 's
    X destructor called

So, as we can see, the destructor of the individual X objects are called if the delete [] syntax is followed, and not if the [] is omitted.

If no destructor is defined, it is not called. Consider the following fragment:


#include <iostream>

class X
{ 
    public:
        ~X();
};

X::~X()
{
    cout << "X destructor called" << endl;
}

int main()
{
    X 
      **a;

    a = new X* [2];
             
    a[0] = new X [2];
    a[1] = new X [2];
                         
    delete [] a;

    return (0);
}

This program produces no messages at all. Why is this? The variable a is defined as a pointer to a pointer. For this situation, however, there is no defined destructor as we do not have something as a 'class pointer to X objects'. Consequently, the [] is ignored.

Now, because of the [] being ignored, not all elements of the array a points to are considered when a is deleted. The two pointer elements of a are deleted, though, because delete a (note that the [] is not written here) frees the memory pointed to by a. That's all there is to it.

What if we don't want this, but require the X objects pointed to by the elements of a to be deleted as well? In this case we have two options:

5.3.2: New and delete and object pointers

The operators new and delete are also used when an object of a given class is allocated. As we have seen in the previous section, the advantage of the operators new and delete over functions like malloc() and free() lies in the fact that new and delete call the corresponding constructors or destructor. This is illustrated in the next example:


    Person
        *pp;                    // ptr to Person object

    pp = new Person;            // now constructed
    ...
    delete pp;                  // now destroyed

The allocation of a new Person object pointed to by pp is a two-step process. First, the memory for the object itself is allocated. Second, the constructor is called which initializes the object. In the above example the constructor is the argument-free version; it is however also possible to choose an explicit constructor:


    pp = new Person("Frank", "Oostumerweg 17", "050 403 2223");
    ...
    delete pp;

Note that, analogously to the construction of an object, the destruction is also a two-step process: first, the destructor of the class is called to deallocate the memory used by the object. Then the memory which is used by the object itself is freed.

Dynamically allocated arrays of objects can also be manipulated with new and delete. In this case the size of the array is given between the [] when the array is created:


    Person
        *personarray;

    personarray = new Person [10];

The compiler will generate code to call the default constructor for each object which is created. As we have seen, the array operator [] must be used with the delete operator to destroy such an array in the proper way:


    delete [] personarray;

The presence of the [] ensures that the destructor is called for each object in the array. Note again that delete personarray would only release the memory of the array itself.

5.3.3: The function set_new_handler()

The C++ run-time system makes sure that when memory allocation fails, an error function is activated. By default this function returns the value 0 to the caller of new, so that the pointer which is assigned by new is set to zero. The error function can be redefined, but it must comply with a few prerequisites, which are, unfortunately, compiler-dependent. E.g., for the Microsoft C/C++ compiler version 7, the prerequisites are:

The Gnu C/C++ compiler gcc, which is present on many Unix platforms, requires that the error handler:

Then again, Microsoft's Visual C++ interprets the returnvalue of the the function as follows:

In short: there's no standard here, so make sure that you lookup the particular characteristics of the set_new_handler function for your compiler. Whatever you do, in any case make sure you use this function: it saves you a lot of checks (and problems with a failing allocation that you just happened to forget to protect with a check...).

The redefined error function might, e.g., print a message and terminate the program. The user-written error function becomes part of the allocation system through the function set_new_handler(), defined in the header file new.h. With some compilers, the installing function is called _set_new_handler() (note the leading underscore).

The implementation of an error function is illustrated below. This implementation applies to the Gnu C/C++ requirements ( The actual try-out of the program is not encouraged, as it will slow down the computer enormously due to the resulting occupation of Unix's swap area):


    #include <new.h>
    #include <iostream>

    void out_of_memory()
    {
        cout << "Memory exhausted. Program terminates." << endl;
        exit(1);
    }

    int main()
    {
        int
            *ip;
        long
            total_allocated = 0;
            
        // install error function
        set_new_handler(out_of_memory);

        // eat up all memory
        puts("Ok, allocating..");
        while (1)
        {
            ip = new int [10000];
            total_allocated += 10000 * sizeof(int);
            printf("Now got a total of %ld bytes\n",
                    total_allocated);
        }

        return (0);
    }

The advantage of an allocation error function lies in the fact that once installed, new can be used without wondering whether the allocation succeeded or not: upon failure the error function is automatically invoked and the program exits. It is good practice to install a new handler in each C++ program, even when the actual code of the program does not allocate memory. Memory allocation can also fail in not directly visible code, e.g., when streams are used or when strings are duplicated by low-level functions.

Note that it may not be assumed that the standard C functions which allocate memory, such as strdup(), malloc(), realloc() etc. will trigger the new handler when memory allocation fails. This means that once a new handler is installed, such functions should not automatically be used in an unprotected way in a C++ program. As an example of the use of new for duplicating a string, a rewrite of the function strdup() using the operator new is given in section 6. It is strongly suggested to revert to this approach, rather than to using functions like xstrdup(), when the allocation of memory is required.

5.4: The keyword inline

Let us take another look at the implementation of the function Person::getname():


    char const *Person::getname() const
    {
        return (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");

    puts(frank.getname());

the following actions take place:

Especially the first part of these actions leads to some time loss, since an extra function call is necessary to retrieve the value of the name field. Sometimes a faster process may be desirable, in which the name field becomes immediately available; thus avoiding the call to getname(). This can be realized by using inline functions, which can be defined in two ways.

5.4.1: Inline functions within class declarations

Using the first method to implement inline functions, the code of a function is defined in a class declaration itself. For the class Person this would lead to the following implementation of getname():

    class Person
    {
        public:
            ...
            char const *getname(void) const
            { 
                return (name); 
            }
            ...
    };
 
Note that the code of the function getname() now literally occurs in the interface of the class Person. The keyword const occurs after the function declaration, and before the code block.

Thus, inline functions appearing in the class interface show their full (and standard) definition within the class interface itself.

The effect of this is the following. When getname() is called in a program statement, the compiler generates the code of the function when the function is used in the source-text, rather than a call to the function, appearing only once in the compiled program.

This construction, where the function code itself is inserted rather than a call to the function, is called an inline function. Note that the use of inline function results in duplication of the code of the function for each invokation 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.

5.4.2: Inline functions outside of class declarations

The second way to implement inline functions leaves a class interface intact, but mentions the keyword inline in the function definition. The interface and implementation in this case are as follows:


    class Person
    {
        public:
            ...
            char const *getname(void) const;
            ...
    };

    inline char const *Person::getname() const
    {
        return (name);
    }

Again, the compiler will insert the code of the function getname() instead of generating a call.

However, the inline function must still appear in the same file as the class interface, and cannot be compiled to be stored in, e.g., a library. The reason for this is that the compiler rather than the linker must be able to insert the code of the function in a source text offered for compilation. Code stored in a library is inaccessible to the compiler. Consequently, inline functions are always defined together with the class interface.

5.4.3: When to use inline functions

When should inline functions be used, and when not? There is a number of simple 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 that using the keyword inline is not really an order for the compiler. Rather, it is a suggestion the compiler may either choose to follow or to ignore.

5.5: Objects in objects: composition

An often recurring situation is one where objects are used as data fields in class definitions. This is referred to as composition.

For example, the class Person could hold information about the name, address and phone number, but additionally a class Date could be used to keep the information about the birth date:


    class Person
    {
        public:
            // constructor and destructor
            Person();
            Person(char const *nm, char const *adr,
                    char const *ph);
            ~Person();

            // interface functions
            void setname(char const *n);
            void setaddress(char const *a);
            void setphone(char const *p);
            void setbirthday(int yr, int mnth, int d);

            char const *getname() const;
            char const *getaddress() const;
            char const *getphone() const;
            int getbirthyear() const;
            int getbirthmonth() const;
            int getbirthday() const;

        private:
            // data fields
            char *name, *address, *phone;
            Date birthday;
    };          

We shall not further elaborate on the class Date: this class could, e.g., consist of three int data fields to store a year, month and day. These data fields would be set and inspected using interface functions setyear(), getyear() etc..

The interface functions of the class Person would then use Date's interface functions to manipulate the birth date. As an example the function getbirthyear() of the class Person is given below:


    int Person::getbirthyear() const
    {
        return (birthday.getyear());
    }

Composition is not extraordinary or C++ specific: in C it is quite common to include structs or unions in other compound types. Note that the composed objects can be reached through their member functions: the normal field selector operators are used for this.

However, the initialization of the composed objects deserves some extra attention: the topics of the coming sections.

5.5.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 the constructor of the composing class. This is illustrated below for the composed class Date in a Person. In this fragment it assumed that a constructor for a Person should be defined expecting six arguments: the name, address and phone number plus the year, month and day of the birth date. It is furthermore assumed that the composed class Date has a constructor with three int arguments for the year, month and day:


    Person::Person(char const *nm, char const *adr,
                    char const *ph,
                    int d, int m, int y)
    : 
        birthday(d, m, y)
    {
        name = xstrdup(nm);
        address = xstrdup(adr);
        phone = xstrdup(ph);
    }

Note that following the argument list of the constructor Person::Person(), the constructor of the data field Date is specifically called, supplied with three arguments. This constructor is explicitly called for the composed object birthday. This occurs even before the code block of Person::Person() is executed. This means that when a Person object is constructed and when six arguments are supplied to the constructor, the birthday field of the object is initialized even before Person's own data fields are set to their values.

In this situation, the constructor of the composed data member is also referred to as member initializer.

When several composed data members of a class exist, all member initializers can be called using a `constructor list': this list consists of the constructors of all composed objects, separated by commas.

When member initializers are not used, the compiler automatically supplies a call to the default constructor (i.e., the constructor without arguments). In this case a default constructor must have been defined in the composed class.

Member initializers should be used as much as possible: not using member initializers can result in inefficient code, and can be downright necessary. As an example showing the inefficiency of not using a member initializer, consider the following code fragment where the birthday field is not initialized by the Date constructor, but instead the setday(), setmonth() and setyear() functions are called:


    Person::Person(char const *nm, char const *adr,
                    char const *ph,
                    int d, int m, int y)
    {
        name = xstrdup(nm);
        address = xstrdup(adr);
        phone = xstrdup(ph);

        birthday.setday(d);
        birthday.setmonth(m);
        birthday.setyear(y);
    }

This code is inefficient because:

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.

This means that when the definition of a Person is changed so that the data member birthday is declared as a const object, the implementation of the constructor Person::Person() with six arguments must use member initializers. Calling the birthday.set...() would be illegal, since these are no const functions.

Concluding, the rule of thumb is the following: when composition of objects is used, the member initializer method is preferred to explicit initialization of the composed object. This not only results in more efficient code, but it also allows the composed object to be declared as a const object.

5.5.2: Composition and reference objects: reference memberinitializers

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 correct initialization of the Configfile &conf_ref reference data member:


    Process::Process(Configfile &conf)
    {
        conf_ref = conf;        // wrong: no assignment
    }

The statement conf_ref = 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 (conf_ref). 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 conf_ref refer? To no variable, since we haven't initialized conf_ref. After all, the whole purpose of the statement conf_ref = conf was to initialize conf_ref....

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


    Process::Process(Configfile &conf)
    :
        conf_ref(conf)      // initializing reference member
    {
        ...
    }

Note that this syntax can be used in all cases where reference data members are used. If int_ref would be an int reference data member, a construction like

    Process::Process(int &ir)
    :
        int_ref(ir)
    {
        ...
    }

would have been called for.

5.6: Friend functions and friend classes

As we have seen in the previous sections, private data or function members are normally only accessible by the code which is part of the corresponding class. However, situations may arise in which it is desirable to allow the explicit access to private members of one class to one or more other classless functions or member functions of classes.

E.g., consider the following code example (all functions are inline for purposes of brevity):


    class A                         // class A: just stores an
    {                               // int value via the constructor
        public:                     // and can retrieve it via
            A(int v)                // getval
                { value = v; }
            int getval()
                { return (value); }

        private:
            int value;
    };

    void decrement(A &a)            // function decrement: tries
    {                               // to alter A's private data
        a.value--;
    }

    class B                         // class B: tries to touch
    {                               // A's private parts
        public:
            void touch(A &a)
                { a.value++; }
    };

This code will not compile, since the classless function decrement() and the function touch() of the class B attempt to access a private datamember of A.

We can explicitly allow decrement() to access A's data, and we can explicitly allow the class B to access these data. To accomplish this, the offending classless function decrement() and the class B are declared to be friends of A:


    class A
    {
        public:
            friend class B;             // B's my buddy, I trust him

            friend void decrement(A     // decrement() is also a good pal
                &what);
            ...
    };

Concerning friendship between classes, we remark the following:

Having thus issued some warnings against the use of friends, we'll leave our discussion of friends for the time being. However, in section 14 we'll continue the discussion, having covered, by that time, the topic of operator overloading.

5.7: Header file organization with classes

In section 2.5.11 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 in 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 will often have 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, 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 (comparable to, e.g., the sys subdirectory). If class MyClass is developed as part of some larger project, create a subdirectory (or subdirectory link) of one of the 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 <myproject/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. The File class has a member gets(String &destination), which reads a line from a file, and stores the line in the String object passed to the gets() member function as reference, while the class String has a member function getLine(File &file), which reads one line from the File object which is passed to the getLine() member function as a reference. 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 File::gets() proceeds as follows:

The solution for this problem is to use a forward class reference before the class definition, and to include the corresponding class header file after the class definition. 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

However, 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 class are involved. But it doesn't work with composition. Assume the class File has a composed data member of the class String. In that case, the class definition of the class File must include the header file of the class String before the class definition 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 definition of the File class is completed.

In cases where classes contain composed objects (or are derived from other classes, see chapter 15) the header files of the classes of the composed objects must have been read before the class definition 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
    {
        public:
            void gets(String &string);
        ...
        private:
            String              // composition !
                line;
    };
    #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 other required header files are either related to classes that are used only within the source files themselves (without being part of the current class definition), or they are related to classless functions (like memcpy()). All headers that are not required by the compiler to parse the current class definition can be mentioned below the class definition.

To summarize, a class header file should be organized as follows:

An example of such an header file is:

    #ifndef _File_h_
    #define _File_h_

    #include <fstream>    // for composed 'instream'

    class String;           // forward reference

    class File              // class definition
    {
        public:
            void gets(String &string);
        ...
        private:
            ifstream
                instream;
    };
                            // for the class String
    #include <project/string.h>

                            // for remaining software
    #include <memory.h>
    #include <sys/stat.h>

    #endif

5.8: Nesting Classes

Classes can be defined inside other classes. Classes that are defined inside other classes are called nested classes.

A class can be nested in every part of the surrounding class: in the public, protected or private section. Such a nested class can be considered a member of the surrounding class. The normal access and visibility rules in classes apply to nested classes. If a class is nested in the public section of a class, it is visible outside the surrounding class. If it is nested in the protected section it is visible in subclasses, derived from the surrounding class (see chapter 15), if it is nested in the private section, it is only visible for the members of the surrounding class.

The surrounding class has no privileges with respect to the nested class. So, the nested class still has full control over the accessibility of its members by the surrounding class.

For example, consider the following class definition:


class Surround
{
    public:
        class FirstWithin
        {
            public:
                FirstWithin();
                int getVar() const
                {
                    return (variable);
                }
            private:
                int
                    variable;
        };
    private:
        class SecondWithin
        {
            public:
                SecondWithin();
                int getVar() const
                {
                    return (variable);
                }
            private:
                int
                    variable;
        };
        // other private members of Surround
};
    
In this definition access to the members is defined as follows: If the surrounding class should have access rights to the private members of its nested classes or if nested classes should have access rights to the private members of the surrounding class, the classes can be defined as friend classes (see section 5.8.3).

The nested classes can be considered members of the surrounding class, but the members of nested classes are not members of the surrounding class. So, a member of the class Surround may not access FirstWithin::getVar() directly. This is understandable considering the fact that a Surround object is not also a FirstWithin or SecondWithin object. The nested classes are only available as typenames. They do not imply containment as objects by the surrounding class. If a member of the surrounding class should use a (non-static) member of a nested class then a pointer to a nested class object or a nested class datamember must be defined in the surrounding class, which can thereupon be used by the members of the surrounding class to access members of the nested class.

For example, in the following class definition there is a surrounding class Outer and a nested class Inner. The class Outer contains a member function caller() which uses the inner object that is composed in Outer to call the infunction() member function of Inner:


    class Outer
    {
        public:
            void caller()
            {
                inner.infunction();
            }
        private:
            class Inner
            {
                public:
                    void infunction();
            };
            Inner
                inner;
    };
    
Also note that the function Inner::infunction() can be called as part of the inline definition of Outer::caller(), even though the definition of the class Inner is yet to be seen by the compiler.

Inline functions can be defined as if they were functions that were defined outside of the class definition: if the function Outer::caller() would have been defined outside of the class Outer, the full class definition (including the definition of the class Inner would have been available to the compiler. In that situation the function is perfectly compilable. Inline functions can be compiled accordingly and there is, e.g., no need to define a special private section in Outer in which the class Inner is defined before defining the inline function caller().

5.8.1: Defining nested class members

Member functions of nested classes may be defined as inline functions. However, they can also be defined outside of their surrounding class. Consider the constructor of the class FirstWithin in the example of the previous section. The constructor FirstWithin() is defined in the class FirstWithin, which is, in turn, defined within the class Surround. Consequently, the class scopes of the two classes must be used to define the constructor. E.g.,

    Surround::FirstWithin::FirstWithin()
    {
        variable = 0;
    }
    

Static (data) members can be defined accordingly. If the class FirstWithin would have a static unsigned datamember epoch, it could be initialized as follows:


    Surround::FirstWithin::epoch = 1970;
    

Furthermore, both class scopes are needed to refer to public static members in code outside of the surrounding class:


    void showEpoch()
    {
        cout << Surround::FirstWithin::epoch = 1970;
    }
    
Of course, inside the members of the class Surround only the FirstWithin:: scope needs to be mentioned, and inside the members of the class FirstWithin there is no need to refer explicitly to the scope.

What about the members of the class SecondWithin? The classes FirstWithin and SecondWithin are both nested within Surround, and can be considered members of the surrounding class. Since members of a class may directy refer to each other, members of the class SecondWithin can refer to (public) members of the class FirstWithin. Consequently, members of the class SecondWithin could refer to the epoch member of FirstWithin as

FirstWithin::epoch

5.8.2: Declaring nested classes

Nested classes may be declared before they are actually defined in a surrounding class. Such forward declarations are required if a class contains multiple nested classes, and the nested classes contain pointers to objects of the other nested classes.

For example, the following class Outer contains two nested classes Inner1 and Inner2. The class Inner1 contains a pointer to Inner2 objects, and Inner2 contains a pointer to Inner1 objects. Such cross references require forward declarations:


    class Outer
    {
        ...
        private:
            class Inner2;       // forward declaration

            class Inner1
            {
                ...
                private:
                    Inner2
                        *pi2;   // points to Inner2 objects
            };
            class Inner2
            {
                ...
                private:
                    Inner1
                        *pi1;   // points to Inner1 objects
            };
            ...
    };
    

5.8.3: Access to private members in nested classes

In order to allow nested classes to access the private members of the surrounding class or to access the private members of other nested classes or to allow the surrounding class to access the private members of nested classes, the friend keyword must be used. Consider the following situation, in which a class Surround has two nested classes FirstWithin and SecondWithin, while each class has a static data member int variable:

    class Surround
    {
        public:
            class FirstWithin
            {
                public:
                    int getValue();
                private:
                    static int 
                        variable;
            };
            int getValue();
        private:
            class SecondWithin
            {
                public:
                    int getValue();
                private:
                    static int 
                        variable;
            };
            static int 
                variable;
    };
    
If the class Surround should be able to access the private members of FirstWithin and SecondWithin, these latter two classes must declare Surround to be their friend. The function Surround::getValue() can thereupon access the private members of the nested classes. For example (note the friend declarations in the two nested classes):

    class Surround
    {
        public:
            class FirstWithin
            {
                friend class Surround;
                public:
                    int getValue();
                private:
                    static int 
                        variable;
            };
            int getValue()
            {
                FirstWithin::variable = SecondWithin::variable;
                return (variable);
            }
        private:
            class SecondWithin
            {
                friend class Surround;
                public:
                    int getValue();
                private:
                    static int 
                        variable;
            };
            static int 
                variable;
    };
    
Now, in order to allow the nested classes to access the private members of the surrounding class, the class Surround must declare the nested classes as friends. The friend keyword may only be used when the class that is to become a friend is already known as a class by the compiler, so either a forward declaration of the nested classes is required, which is followed by the friend declaration, or the friend declaration follows the definition of the nested classes. The forward declaration followed by the friend declaration looks like this:

    class Surround
    {
        class FirstWithin;
        class SecondWithin;
        friend class FirstWithin;
        friend class SecondWithin;

        public:
            class FirstWithin

            ... (etc)
    
Alternatively, the friend declaration may follow the definition of the classes. Note that a class can be declared a friend following its definition, while the inline code in the definition already uses the fact that it will be declared a friend of the outer class. Also note that the inline code of the nested class uses members of the surrounding class which have not yet been seen by the compiler. Finally note that the variable variable that is defined in the class Surround is accessed in the nested classes as Surround::variable:

    class Surround
    {
        public:
            class FirstWithin
            {
                friend class Surround;
                public:
                    int getValue()
                    {
                        Surround::variable = 4;
                        return (variable);
                    }
                private:
                    static int 
                        variable;
            };
            friend class FirstWithin;

            int getValue()
            {
                FirstWithin::variable = SecondWithin::variable;
                return (variable);
            }
        private:
            class SecondWithin
            {
                friend class Surround;
                public:
                    int getValue()
                    {
                        Surround::variable = 40;
                        return (variable);
                    }
                private:
                    static int 
                        variable;
            };
            friend class SecondWithin;
    
            static int 
                variable;
    };
    
Finally, we want to allow the nested classes to access each other's private members. Again this requires some friend declarations. In order to allow FirstWithin to access SecondWithin's private members nothing but a friend declaration in SecondWithin is required. However, to allow SecondWithin to access the private members of FirstWithin the friend class SecondWithin declaration cannot be plainly given in the class FirstWithin, as the definition of SecondWithin has not yet been given. A forward declaration of SecondWithin is required, and this forward declaration must be given in the class Surround, rather than in the class FirstWithin. Clearly, the forward declaration class SecondWithin in the class FirstWithin itself makes no sense, as this would refer to an external (global) class FirstWithin. But the attempt to provide the forward declaration of the nested class SecondWithin inside FirstWithin as class Surround::SecondWithin also fails miserably, with the compiler issuing a message like
`Surround' does not have a nested type named `SecondWithin'
The right procedure to follow here is to declare the class SecondWithin in the class Surround, before the class FirstWithin is defined. Using this procedure, the friend declaration of SecondWithin is accepted inside the definition of FirstWithin. The following class definition allows full access of the private members of all classes by all other classes:

    class Surround
    {
        class SecondWithin;
        public:
            class FirstWithin
            {
                friend class Surround;
                friend class SecondWithin;
                public:
                    int getValue()
                    {
                        Surround::variable = SecondWithin::variable;
                        return (variable);
                    }
                private:
                    static int 
                        variable;
            };
            friend class FirstWithin;
    
            int getValue()
            {
                FirstWithin::variable = SecondWithin::variable;
                return (variable);
            }
        private:
            class SecondWithin
            {
                friend class Surround;
                friend class FirstWithin;
                public:
                    int getValue()
                    {
                        Surround::variable = FirstWithin::variable;
                        return (variable);
                    }
                private:
                    static int 
                        variable;
            };
            friend class SecondWithin;
    
            static int 
                variable;
    };
    

5.8.4: Nesting enumerations

Enumerations may also be nested in classes. For example, a class DataStructure may be traversed in a forward or backward direction. Such a class can define an enumerator Traversal having the values forward and backward. Furthermore, a member function setTraversal() can be defined requiring either of the two enumeration values. The class can be defined as follows:

    class DataStructure
    {
        public:
            enum Traversal
            {
                forward,
                backward
            };
            setTraversal(Traversal mode);
            ...
        private:
            Traversal
                mode;
            ...
    };
    
Within the class DataStructure the values of the Traversal enumeration can be used directly. For example:

    void DataStructure::setTraversal(Traversal modeArg)
    {
        mode = modeArg;
        switch (mode)
        {
            forward:
                ....
            break;

            backward:
                ....
            break;
        }
    }
    
Ouside of the class DataStructure the name of the enumeration type is not used to refer to the values of the enumeration. Here the classname is enough. Only if a variable of the enumeration type is required the name of the enumeration type is needed, as illustrated by the following piece of code:

    void fun()
    {
        DataStructure::Traversal                // enum typename required
            localMode = DataStructure::forward; // enum typename not required

        DataStructure
            ds;
                                                // enum typename not required
        ds.setTraversal(DataStructure::backward);
    }
    
Again, if DataStructure would define a nested class Nested in which the enumeration Traversal would have been defined, the two class scopes would have been required. In that case the former example would have to be coded as follows:

    void fun()
    {
        DataStructure::Nested::Traversal
            localMode = DataStructure::Nested::forward;

        DataStructure
            ds;

        ds.setTraversal(DataStructure::Nested::backward);
    }