C++ Annotations Version 4.0.0

Chapter 4: 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. Mail to Frank Brokken or use an e-mail form. Please state the concerned document version, found in the title. If you're interested in a printable PostScript copy, use the form, or better yet, pick up your own copy by ftp from ftp.icce.rug.nl/pub/http.

In this chapter classes are the topic of discussion. Two special member functions, the constructors 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.

4.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.

4.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 4.4.3) will `automatically' call the member initializers, and therefore does perform some actions. We postpone the discussion of such constructors to 4.5.).

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 when the function is called.

When an object is a global variable or a static 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.h> // a class Test with a constructor function class Test { public: // 'public' function: Test(); // the constructor }; Test::Test() // here is the { // definition puts("constructor of class Test called"); } // 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()

4.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 function in which the object is defined is about to return. For static or global variables the destructor is called before the program terminates. Even when a program is interrupted using an exit() call, the destructors are called for objects which exist at that time.

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

A 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.

4.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: 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 *n) { free(address); address = xstrdup(n); } void Person::setphone(char const *n) { free(phone); phone = xstrdup(n); }

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. 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 4.1.4.1, 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.h> 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 printf(). This unesthetic feature can be remedied with a little more code:

void printperson(Person const &p) { if (p.getname()) printf("Name : %s\n", p.getname()); if (p.getaddress()) printf("Address : %s\n", p.getaddress()); if (p.getphone()) printf("Phone : %s\n", p.getphone()); }

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

4.1.4: Constructors with arguments

In the above definition 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 definition, 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; }

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.

4.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 = strdup("without name"); printf("Test object without name created\n"); } Test::Test(char const *name) { n = strdup(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

4.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.1 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 objects 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 with const references.

4.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 usage of these operators is given below. A pointer variable to an int is used to point to memory to which is allocated by 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, such as is the case with 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).

4.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.h> class X { public: ~X(); }; X::~X() { cout << "X destructor called" << endl; } void 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; }

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.h> class X { public: ~X(); }; X::~X() { cout << "X destructor called" << endl; } void main() { X **a; a = new X* [2]; a[0] = new X [2]; a[1] = new X [2]; delete [] a; }

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:

4.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.

4.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:

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.h> 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; 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 standard C functions which allocate memory, such as strdup(), malloc(), realloc() etc. may not 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.

4.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.

4.4.1: Inline functions within class definitions

Using the first method to implement inline functions, the code of a function is defined in a class definition 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, and shows that 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.

4.4.2: Inline functions outside of class definitions

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.

4.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.

4.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 composted 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.

4.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 = strdup(nm); address = strdup(adr); phone = strdup(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 = strdup(nm); address = strdup(adr); phone = strdup(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.

4.5.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 configuarion 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 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. Actually, the whole purpose of the statement conf_ref = conf was after all 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.

4.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 9 we'll continue the discussion, having covered, by that time, the topic of operator overloading.