Essentialssec11.htm

Essentials of C++: Section XI


Classes

Another way to extend the C++ type system is through the use of class declarations. The class system is C++'s way of implementing object-oriented programming . The class system therefore allows for encapsulation, inheritance, and polymorphism.
 

A. Encapsulation
Both data (the properties of instances of a class) and functions (the operations that instances can perform) can be declared as integral parts of a class type. They then become parts of each instance of that class type. Data and functions that are appropriately declared can be made inaccessible to the rest of the program. Such class elements are said to be encapsulated . Other data and functions can be declared as public, in which case they are available to the rest of the program. Careful design of classes, based on encapsulation, supports data abstraction and the software engineering principles widely recognized as important to software re-use and bug-free code.
 

B. Inheritance
Inheritance involves the ability to create a new class based on an already existing class (or classes in the case of multiple inheritance) that has a subset of the properties of the desired class. Both data (properties) and functionality can be inherited.
 

C. Polymorphism
Polymorphism allows instances of different classes to respond to the same message or function call in the way appropriate for that instance type. It is related to the idea of dynamic binding where the system does not decide until run-time which function code will be executed for a given function call.
 

D. Class Declarations
Ignoring inheritance for the moment, a class declaration takes the following general form:

class class-name
    {public:
      // variable, function and other declarations that are to be public

      private:
         variable, function and other declarations that are to be encapsulated
    };

The placement of the public and private sections can be swapped. Everything placed in the private part is encapsulated. Variables declared inside a class represent the data or properties of instances of the class and usually are declared in the private part of the class. Such variables are often referred to as data members or member data.

Functions declared as part of a class are often referred to as member functions. Member functions that other parts of the program must access for any purpose, including to get access to or manipulate the private data members of instances of the class, must be declared in the public part of the class. Functions used only by the member functions themselves and which are otherwise unavailable to the program should be declared in the private part.

Aside from their placement inside a class, member data and function declarations are the same as non-member declarations. There are, however, two special types of member functions. First, all classes need one or more constructors. These are functions automatically called when an instance of a class is declared. Their purpose is to create or 'construct' an instance of the class. The data members of the instance are initialized according to the instructions in the constructor function.

C++ will create a default constructor that simply creates the instance, but, usually, the programmer creates one or more constructors for a class to perform initializations unique to instances of that class. Constructors always have the same name as the class they are members of and they have no return type - not even 'void'. Their parameter list looks the same as any other function declaration with the parameters themselves usually being used to provide the initial property values for the instance being created. For example, if one is declaring a 'person' class, the constructor declaration might be:

Person(char* nm, int ag, char* streetAddr, char* cty, char* stte, char* zip);

If we assume that the 'person' class includes the data members:

name,     age,     streetAddress,     city,     state,    zip

then one could interpret the parameter list above to indicate that the parameter 'nm' holds the value to be given to the data member 'name; 'ag' holds the value to be given to 'age', 'streetAddr' holds the value for streetAddress, etc. As with any other set of variables, the parameter names and data member names must be different. For that reason, 'stte', for example, is used in the parameter list so that 'state' could be the data member name.

All constructors for a specific class have the same name and the same return type. It is via function overloading that multiple constructors are created.

Unlike other member functions, constructors cannot be inherited. It is possible, however, for a constructor of an inheriting class to call the inherited class's constructor. Since constructors are used in instance declarations (see Part D below), they should be declared in the public section. Note: constructors are not called except indirectly as part of instance declarations and as part of constructors for inheriting classes.

When initializing an instance with another instance (as might happen when declaring an instance and immediately initializing it with the values from another instance) and when passing an instance as a value parameter to a function, the property values of one instance are copied to another instance. Such copying requires a copy constructor. C++ automatically provides one that works as long as the class declaration does not include pointer, class, struct, or array-based data members. In these situations it may be necessary for programmers to declare their own copy constructors.

The second special type of member function declaration is the destructor. Any instance for which special code must be executed when the instance disappears must have a destructor containing that special code. While there can be multiple constructors - based on the idea of function overloading, there can be only one destructor per class. It is called whenever the duration of a class instance ends.

The format for the destructor is:

~class-name();

Note that destructors always start with a tilde (~), use the class name (as with constructors), have no return type, and no parameters. They cannot be inherited but they can be explicitly called.

A simple declaration for the 'Person' class might be:

 
	class Person
 	{public:
		Person(char* nm, int ag, char* streetAddr, char* cty, char*
		       stte,  char* zip);
~Person();
char* ProvideName(); int
ProvideAge(); // 'Provide...' functions for other properties
void ChangeName(char* nm); void ChangeAge(int ag); // 'Change...' functions for the other properties
private: char* name; int age; char* streetAddress; char* city; char* state; char* zipCode // zip codes are not considered to be integers // so they are declared as strings. };
Discussion in "The Story of C++
The Basic Constructor
Constructors and Arrays / Default Constructors
Destructors

E. Class Definitions
Member functions are defined the same way other functions are defined. That definition can be part of an in-line function declaration (See Part 'F' below) in the class declaration or it can be part of a separate definition as in standard function definitions. Usually the class declaration is in a header file and the definitions are in a separate file that contains an 'include' directive for the corresponding header file.

Since the system must know that a specific definition belongs to a specific class, the scope resolution operator, '::', is used with member function definitions. To define the member function that returns the age of a person one might write:

int Person :: ProvideAge()
{
     return age;
}

To define the member function 'ChangeAge', one might write:

void Person :: ChangeAge(int ag)
{
      age = ag;
}

Note that the first line of each definition is the same as the corresponding declaration except that the class name and scope resolution operator are inserted. The same approach is used to define constructors and destructors. Below are incomplete definitions for each:

 
	Person :: Person(char* nm, int ag, char* streetAddr, 
			 char* cty, char* stte, char* zip)

        { 
		// the code for the constructor would go here
	}

	Person :: ~Person()
	{ 
		// the code for the destructor would go here
	}
Discussion of the scope resolution operator in "The Story of C++"

F. Inline Member Functions
The section describing functions defined inline functions. To declare a member function as inline one can use the procedure described there or one can use a form specific to class member functions. The general form (inside the class declaration) is:

some function declaration {code for the function}

One line member functions are ideal candidates for inline functions. To re-code 'ChangeAge' as an inline function one would write the following in the class declaration and remove the definition code:

void ChangeAge(int ag) {age = ag;}

The code that was in the definition has been moved into the declaration. Note that the declaration does NOT end with a semi-colon.

 

G. Creating Class Instances
Once a class declaration has been written, the class name can be used as a type name in any file that includes the class declaration. In other words, since class declarations are type definitions, class names can be used anywhere type names are used. The general format for declaring an instance of a class (called instantiating an instance) is:

class-name instance-name(any pararmeters required of the constructor);

Note that the instantiation process automatically calls a constructor. If a class declaration has multiple constructors, the one called is determined using the same rules used in other function overloading. If the constructor to be used has no parameters, the parentheses are dropped. To declare an instance of class 'Person' one might write:

Person pablo("pablo", 8, "2012 6th Street", "Las Vegas", "New Mexico", "87701");

This instantiation process can be seen as declaring and initializing the identifier 'pablo' as a variable of type 'Person' with the property values shown. A variable such as 'pablo', which represents an instance of a specific class, is ofter referred to as an instance variable.

One must be careful when creating an array of instances of a class type because one is instantiating as many instances of the class as there are elements in the array and a constructor must be called for each instance. The following code will not work:

Person person[10]; // This code will fail

because there is no constructor for the class 'Person' that has no parameters. One can fix this by declaring and defining a constructor with no parameters or by defining a constructor with default values (Section III.A) for all parameters. Either way, the properties for each instance of 'Person' in the array may not be what the user wants. If so, the program must include explicit code for giving values to these properties.
 

H. Using Instances
Instance variables are like any other structured variables. They can be passed as parameters, assigned to other instances of the same class, and returned by functions. They can also be used with appropriately overloaded operators.

The private members of an instance are simply unaccessable from outside the instance. To access the public members of an instance one can use the dot operator:

instance-name.public-member-of-the-instance

When the public member is a function (which is most likely to be the case since data members tend to be private), the part of the code to the right of the 'dot' is written the same as any other function call. For example:

person1.ProvideAge();

or

person[3].ChangeAge(30);

In the language of object-oriented programming, function calls like this are often referred to as messages. Using this terminology one can interpret the above as:

"Send the 'ProvideAge' message to person1."

or

"Send the 'ChangeAge' message with a value of 30 to the fourth element of the 'person' array."

In other words, the code is explicitly asking that something be done by 'person1' or 'person[3]'. This is how the system determines which instance is being referred to. Both these function calls really have an implicit or hidden argument - the specific instance under consideration. At times one needs to refer explicitly to this instance. Therefore, each member function has a built-in local variable referred to as this which is a pointer to the instance "sent the message". A common use for 'this' is in member functions that need to return the instance itself.

When using pointer references to class instances, the dot operator notation can be cumbersome because of the precedence rules. Consider the example:

Person* ptrPerson;
(*ptrPerson).ProvideAge();

The parentheses must be used because the dot operator has higher precedence than the indirection operator. To simplify matters C++ provides another operator for accessing members, ->. Using this operator the above code becomes:

Person* ptrPerson;
ptrPerson -> ProvideAge();

 

I. The Syntax of Inheritance
To indicate that a class inherits from another class, the first line of the declaration includes a colon followed by the name of the class (or classes) inherited from. The simplest form of this is:

class some-new-class : the-class-inherited-from

This declares 'some-new-class' as a derived class type inheriting from base class, 'the class-inherited-from'. Since the new class only inherits from one class, we have an example of single inheritance.

To provide some control over what gets inherited and the kinds of access users of the derived class have to members of the base class, C++ includes additional keywords both for the base class declaration and the inheritance declaration.

In addition to the public and private sections of a class declaration, there can be a protected section. Private members of a base class are not directly accessible to member functions of derived classes. They can only be accessed through the base class's interface. To allow member data and functions to be directly accessible to a derived class but not accessible outside the class, such member data and functions should be placed in the 'protected' section.

This level of access control is handled by the base class. The declaration of the derived class can further refine access by adding the words 'public', 'private' or protected in front of each base class inherited from. The word 'public' signifies that there is no change in access either by users or classes that inherit from this derived class. On the other hand, 'private' signifies that even 'public' and 'protected' members of the base class are now treated as if they were private. When 'protected' is used, the public or protected members of the base class become protected. Note that in all cases, private members in the base class remain private.

The default is 'private' so 'public' must be used if one wishes to keep access rights the same. For documentation purposes it is recommended that the desired option be included in the declaration. The basic form of a declaration involving single inheritance now becomes:

class some-new-class : [public or private] the-class-inherited-from

Thus, to declare a new class 'Student' that inherits from 'Person' and allows the same level of access to the protected and public members of 'Person', one would write:


	class Person
	{public:
                Person(char* nm, int ag, char* streetAddr, char* cty, 
		char* stte, char* zip);
	        ~Person();
		char* ProvideName();
		int ProvideAge();

		     // 'Provide...' functions for other properties

		void ChangeName(char* nm);
		void ChangeAge(int ag);

		     // 'Change...' functions for the other properties

	 protected:
	 	char* name;
		int age;
		char* streetAddress;
		char* city;
		char* state;
		char* zipCode  // zip codes are not handled as integers so
			       // they are declared as strings
	};

	class Student : public Person
	{
		// member declarations new to class Student
	}
Although not used as often, it is possible to have multiple inheritance - where one class inherits from more than one class. There are a number of issues involved in this and you are encouraged to check other references for a more thorough discussion. Syntactically, it is easy to accomplish. You simply add a comma followed by the access type (public, protected, or private) and the class name for each additional base class. If class 'X' is to inherit from classes 'Y' and 'Z' one might write:

class X : public Y, protected Z;

 

J. Friend Functions
C++ allows a breach in the encapsulation rules by allowing one to declare functions or classes as friends of a class. Friend functions or classes have full access to the public, protected, and private members of any class for which they have been declared as friends. It is as if the friend functions or member functions of the friend class were members of the class.

To declare that a function is a friend of (has full access to) a class, one includes the declaration of that function in the class declaration with the word 'friend' in front of the function declaration. By putting the word friend here, you are declaring that this function is NOT a member function of the class but it does have access to the private and protected members of that class. The definition of the friend function may or may not be included in the file containing the definitions for the class's member functions.

One can also declare a whole class to be a friend of another class. This allows ALL member functions of the friend class to have full access to all the members of the other class. The general form for this is:

friend some-class-name;

where this line is placed inside a class declaration and 'some-class-name' is the name of a class known in the file holding the class declaration. If the class 'Teacher' was to be declared as a friend of 'Student' one could write:

class Teacher;
class Student : public Person
{
       friend Teacher;
       // member declarations new to class Student
}

The first line of this example is an incomplete declaration of the 'Teacher' class. It acts as a promise that the class is declared somewhere. (The other way to accomplish the same goal would be to use the 'include' preprocessor directive to copy in the code for the declaration of 'Teacher'.) By including 'Teacher' as a friend of 'Student', any instance of 'Teacher' has full access to ALL the members of instances of the 'Student' class. (Sounds right - don't students feel they have no privacy from teachers and administrators!)


K. Static Members
As we have declared data members so far, whether (public, protected, or private), there is a unique copy of each data member in each instance. Sometimes, however, one wants a data member that says something about the whole class. To declare such a data member in C++ one puts the keyword static in front of the declaration. Consider a data member for the class 'Person' that held the age of the oldest person. This is not a property of any specific person but of the class. To declare it as a 'static' data member one would include the following line inside the class declaration for 'Person'.

static int oldestAge;

This data member exists whether or not any instances of 'Person' exists - which is not true of non-static data members because they belong to specific instances. This means that a static variable requires both a declaration (just given) and a definition. The definition must be outside the class declaration, usually in the file for the member functions. The form is:

type class-name :: static-variable-name

as in:

int Person :: oldestAge;

This form can be extended to include an initialization as in:

int Person :: oldestAge = 100;

One can access static variables in the same way one can access other data members - following the same rules about public, protected, and private membership. In addition, since these variables exist independent of any specific instance, one can declare static member functions to access them. Such functions are declared the same as member functions except that they too have the keyword 'static' placed before the declaration. To declare a static member function to update the 'oldestAge' the following code would be placed in the public section of the class declaration:

static void ChangeOldestAge(int ag);

To call this function one uses the class name instead of an instance name as in:

Person :: ChangeOldest(110);

Such functions, since they do not refer to a specific instance, do not have access to the data members of the class.

 

L. Implementing Polymorphism
All member functions declared so far are linked to their definitions at link-time. In other words, at run-time, the system already knows what code to execute if a function is called. To implement polymorphism and the dynamic binding (Part 'C' above) it requires, this link between the declaration and definition must be postponed until the moment the function is called. The idea is that there are multiple (polymorphic) functions with the same name and each time a polymorphic function is called, the system stops for a moment to figure out which version of the function to actually call. Since not all functions are polymorphic, one must let the compiler and linker know which set of functions are polymorphic and require run-time linkage.

In C++ polymorphic functions can only be those belonging to classes involved in an inheritance structure. Such a structure represents the one or more base classes and all the classes that derive directly or indirectly from this (these) base classes. (See Chapter 11, Section 1, Figure 2 for an example of an inheritance structure.) To state which functions are to have their linkage postponed until run-time, one adds the word virtual in front of the highest declaration of this function in the hierarchy.

This text does not deal further with polymorphism and you should check elsewhere if you want to include polymorphic functions in your code.
 

Links to 'The Story of C++" and other documents

Chapter on Object-Oriented Programming
Implementing Encapsulation
Inheritance
Constructors
Destructors

Top of Section Main Menu of Essentials of C++ Next Section