Advanced OOP using C++

What is Object-Oriented Programming?

Published:  July 28, 2008
Revised:  August 4, 2008
By Richard G. Baldwin

File:  AdvCpp00110


Preface

General

This document provides an introduction to the concept of object-oriented programming (OOP) gleaned from several different books. While it is possible to use C++ without programming in an object-oriented way, in order to program using the OOP paradigm, it is necessary to use C++, Java, C#, or some other similar programming tool that is designed to support OOP.

Viewing tip

I recommend that you open another copy of this document in a separate browser window and use the following links to easily find and view the figures and listings while you are reading about them.

Figures

Listings

Supplementary material

I recommend that you also study the other lessons in my extensive collection of online programming tutorials.  You will find a consolidated index at www.DickBaldwin.com.

Differences between procedural and object-oriented programming

OOP is based on the following design guideline:

The expression of an algorithm should model the application domain that it supports.

Stated differently, the solution to the problem should resemble the problem.  Observers of the solution should be able to recognize the problem without necessarily knowing about it in advance. For example, a good example of this guideline is the use of OOP to develop a stack class from which stack objects can be created. This will be discussed in some detail later in the course.

Procedural programming

The classic approach to procedural programming often begins with development of the functions and procedures and then progresses to development of the data structures. Procedural programming does not always result in a solution that resembles the problem because it emphasizes the functions rather than the data (emphasizes the procedures rather than the objects).

Object-oriented programming

The world and its applications are not organized into values and procedures that are separate from one another. People who solve problems in other crafts do not perceive the world that way. They deal with their problem domains by concentrating on the objects and letting the characteristics of those objects determine the procedures to apply to them.

To build a house, grow a tomato, or repair a carburetor, you first think about the object and its purpose and behavior. Then you select your tools and procedures. The solution fits the problem.

The world is object-oriented, and the object-oriented programming paradigm expresses computer programs in ways that model how people perceive the world.

Vocabulary

OOP involves a whole new vocabulary, which is different from or supplemental to the vocabulary of procedural programming. For example, given the OOP vocabulary (or jargon) the object-oriented programmer defines an abstract data type by encapsulating its implementation and its interface into a class. Inherited abstract data types are derived subclasses of base classes. Within the program the programmer instantiates objects of classes and sends messages to the objects by calling the class' methods.

If a program is object oriented, it adheres to the characteristics of:

Also, it

Much of this terminology will become clear as we learn how to use the various OOP features of C++.

The characteristics of an object-oriented program

An object-oriented program has four fundamental characteristics:

Note that some authors combine the first and second of these characteristics, resulting in only three fundamental characteristics.

The object

Before getting into a detailed discussion of the four characteristics, let's consider some basic questions.

Hopefully you will be able to develop your own answers as you progress through this course of study.

An instance of a data type

Simply stated, an object is an instance of a data type. The function in Listing 1 declares two items. The first item is an instance of a simple integer of type int. The second item is an instance of an abstract data type or a class named Date.

Listing 1. Two instances of a type.
void f(){
  ...
  int ndays;//instance of an int
  Date cdt; //instance of the abstract data type Date
  ...
}//end function

You already know where the integer data type named int comes from. It is a type, which, along with float, long, double, etc., is intrinsic to both C and C++. At this point, however, you don't know about the abstract data type named Date. During this course of study, you will learn how to define new data types and then to create instances of them (objects).

So, what are the objects? They are the variables that you declare using abstract data types.

Abstraction

The first of the four major characteristics of an object-oriented program is Abstraction.

Abstraction is the definition of an abstract data type, which includes the data type's representation and behavior.

An abstract data type is a new type (not intrinsic to C or C++). It is not one of the primitive data types that are built into the programming language (such as int, long, and float). The data representation and behavior of the intrinsic or primitive types is already known to the compiler. An abstract data type is not known to the compiler. The C++ programmer defines the type's format and behavior in a class definition.

A class definition

The code fragment in Listing 2 creates an abstract data type by expressing its format and behavior in the definition of a class. The compiler knows nothing about this data type until it sees the class definition.

Listing 2. Defining an abstract data type.
class Date{
  int month, day, year;//data elements of the class
  public:
  Date(int mo,int da,int yr);//constructor for the class
  int operator+(int n);//overloaded + operator adds dates
  ...//other member functions to define behavior
};//note the required semicolon

Not a complete class definition

The code fragment in Listing 2 is far from being a complete class definition.  In addition to the fact that more functions are required to define the behavior of an object of the Date class, only the prototypes are shown for the constructor and the overloaded operator function.

An abstract data type named Date

This abstract data type named Date has month, day, and year data members. The overloaded operator function suggests that it supports the addition of an integer number of days to the date encapsulated in an object of the class. The class could be expanded to support addition and subtraction of dates, comparison of dates using overloaded relational operators, etc. It could also be expanded to support behavior that might be peculiar to the date operations necessary in the specific application for which it will be used.

Object declarations
A C++ program declares instances of abstract data types the same way that it declares instances of primitive data types. Each instance of an abstract data type is an object.

Having defined the new type, you can create instances of the type (objects) and deal with those objects much as you would deal with any other variables created from the primitive data types.

To instantiate an object

The jargon instantiate is used by programmers as a verb to describe the creation of an instance of a data type, or the creation of an object. When an object-oriented program declares an instance of an abstract data type, the program has instantiated an object.

Encapsulation

General

The second of the four major characteristics of an object-oriented program is encapsulation.

If abstraction is the definition of a C++ class, then encapsulation is its design or implementation. A programmer encapsulates the data representation and behavior of an abstract data type into a class, giving it its own implementation and interface.

Hiding the implementation

An encapsulated design hides its implementation from the class user and reveals only its interface. Listing 3 is an example of an encapsulated class:

Listing 3. Encapsulation.
class Date{
  //--- Implementation is private by default
  int month, day, year;//private data members
  ...//other private data members or functions
  public://the class interface is made public
  Date(int mo,int da,int yr);//public constructor
  Date operator+(int n);//public overloaded operator
  ...//other public interface functions
};// note the required semicolon

In the code fragment in Listing 3, the private data members and functions can be accessed only by other member functions of the class. They cannot be accessed by program statements outside the class. Thus, they are hidden from the using program.

Don't know, don't care

A user of a class shouldn't know and shouldn't care about the details of implementation -- only that it works. With encapsulation of the implementation, the class designer could totally change the implementation -- perhaps changing the date representation to a long integer count of days -- and the using programs would be unaffected.

Data hiding

The interface that is visible to the user of the class consists of the public member functions. The class user reads and modifies values in the data representation by calling public member functions. If the class is properly designed, the user cannot modify the data members of the object without going through the prescribed interface.

The user interface

The class interface in the fragment in Listing 3 consists of the prototypes for:

Again let me point out that the nomenclature you see in the interface to the class in Listing 3 consists only of the prototypes of the member functions. The definition of the member functions must be provided elsewhere.

Not a good design by default

An object-oriented design is not a good design by default. In an attempt to produce good designs, experienced C++ programmers generally agree on certain design standards for C++ classes. For example, the data members are usually private and constitute most of the implementation. The interface consists only of member functions and includes few if any data members. The member functions in the interface restrict access to the data members. The interface is generic in that it is not bound to any particular implementation. Hence, the class author should be able to change the implementation without affecting the using programs so long as the interface doesn't change.

Methods and messages

Method is another name for C++ member functions. Methods may be constructors, destructors, functions, and overloaded operators. They define the class interface. The constructor and overloaded plus operator in the previous example are methods.

A message is the invocation of a method, which in C++ is the same thing as calling the public member function. The program sends a message to an object telling it to execute the method and sometimes provides parameters for the method to use.

There are three different kinds of methods characterized by how they support the class definition:

Functional methods

The program fragment in Listing 4 shows three functional methods added to the Date class from the previous example.

Listing 4. Three functional methods.
class Date{
  int month, day, year;// private data members
  public:
  Date(int mo, int da, int yr);//public constructor
  Date operator+(int n);//overloaded operator function
  void Display();//display the date
  void AdjustMonth(int m);// +/- m months
  int DayOfWeek();//return 0-6 = Sun-Sat
};// note the required semicolon

Three new functional methods

The three new methods in the above example are highlighted in boldface and illustrate three typical method variants. The first method tells the object to do something. In this case the object is told to display itself. The class's implementation knows how to display the object's data representation.

The second method tells the object to change itself. In this case the object is told to adjust its month data value up or down by the integer argument in the method's parameter. This method is an example of one that includes a parameter.

The third method is one that returns a value. In this case, the returned value is the day of the week.

These three methods define behavior related to the functional properties of the class. The class is designed to represent a calendar date, and you want it to behave like a date.

Data type methods

Data type methods make a class act like a primitive data type by giving it properties similar to those of a primitive data type. These properties are usually implemented as overloaded operators in the class interface. The program fragment in Listing 6 shows the addition of methods that compare dates, assign them to one another, and do some arithmetic on them.

Listing 5. Data type methods.
class Date{
  int month,day,year;//private data members
  public:
  Date(int mo,int da,int yr);//public constructor
  void Display();//display the date
  void AdjustMonth(int m);// +/- m months
  int DayOfWeek();//return 0-6 = Sun-Sat
  // --- arithmetic operators
  Date operator+(int n);
  Date operator-(int n);
  int operator-(Date &dt);//note the reference operator
  // --- assignment operators
  Date& operator=(Date &dt);
  // --- relational operators
  int operator==(Date &dt);
  int operator!=(Date &dt);
  int operator<(Date &dt);
  int operator>(Date &dt);
};//note the required semicolon

Prototypes only

As mentioned earlier, the statements that appear in the interface portion of the class are simply the prototypes of the functions that provide the interface. Without access to some documentation or the definitions of those functions, we cannot be certain just what the functions do. However, we can recognize the operators that are being overloaded and know in general how we expect them to behave. A good OOP design causes the overloaded operators to perform as normally expected on data members of the class to which they apply.

Implicit conversion methods

The C++ language handles implicit conversion of primitive data types. If you write an expression with an int where the compiler expects a long, the compiler knows how to make the conversion and does so.

If you write an expression where the compiler expects an abstract data type, and you provide a different data type, primitive or abstract, the compiler does not know how to deal with that. Similarly, if the expression expects a primitive data type, and you use an abstract data type, the compiler does not know what to do. In either case, unless you have provided implicit conversion methods, the compiler issues an error message and refuses to compile the program.

You can add implicit conversion methods to a class so that the compiler knows how to convert from one data type to another. We will learn more about this later in the course of study.

Inheritance

The third major characteristic of an object-oriented program is inheritance. A new class can inherit the characteristics of an existing class. The original class is called the base class, and the new class is called the derived class. (They are also sometimes referred to as the super-class and the subclass.)

The derived class inherits the data representation and behavior of the base class except where the derived class modifies the behavior by overriding member functions. In addition, the derived class adds behavior that is unique to its own purpose.

A program can instantiate objects of a base class as well as those of a derived class. If the base class is an abstract base class -- one that exists only to be derived from -- the program may not instantiate objects of the base class.

The purpose of inheritance

The C++ inheritance mechanism lets you build an orderly hierarchy of classes. When several of your abstract data types have characteristics in common, you can design their commonalties into a single base class and separate their unique characteristics into unique derived classes. That is the perhaps the fundamental purpose of inheritance.

For example, suppose you are building a program dealing with airships. All airships have altitude and range parameters in common. Therefore, you could build a base Airship class containing information about range and altitude.

From this base class, you might derive a Balloon class and an Airplane class. The Balloon class might add parameters dealing with passenger capacity and what makes it go up (helium, hydrogen, or hot air). The Balloon class would then be able to deal with altitude, range, passenger capacity, and what makes it go up.

The Airplane class might deal with engine type (jet or propeller) and cargo capacity. The Airplane class could then deal with altitude, range, engine type, and cargo capacity.

Application frameworks

While examples of this "database" type are interesting to ponder, there are other, possibly more important uses of the inheritance property. For example, application frameworks make it possible to create classes that deal with the inherent complexity of an API in a base class while still allowing the user to create derived classes that deal with the customized aspects of a Graphical User Interface.

Modeling the ISA relationship

Object-oriented designers strive to use inheritance to model relationships where a derived class is a kind of the base class. A car is a kind of vehicle. A programmer is a kind of employee, which is a kind of person. This relationship is called the ISA relationship.

Multiple inheritance problems
Note that a number of serious problems can arise when multiple inheritance is improperly used.

Multiple inheritance

A derived class can inherit the characteristics of one or more base classes. This leads to both single and multiple inheritance. For example, a sofa-bed is a sofa and also it is a bed, both of which are items of furniture. This might lead to a base class for Furniture and two derived classes for Sofa and Bed, each derived from Furniture. This might then lead to another multiply derived class, Sofabed, which is derived from both Sofa and from Bed.

As another example, an amphibious airplane is a boat and it is an airplane, both of which are vehicles. Note however that a car-phone is not a car and a telephone. Be careful with inheritance, particularly multiple inheritance. Don't overuse it, and don't use it at all unless you really understand the ramifications.

Polymorphism

General

Polymorphism (from the Greek, meaning "many forms") is the quality that allows one name to be used for two or more related but technically different purposes. The purpose of polymorphism as it applies to OOP is to allow one name to be used to specify a general class of actions. Within a general class of actions, the specific action to apply is determined by the type of data involved. More generally, the concept of polymorphism is the idea of "one interface, multiple methods".

Two kinds of polymorphism

As you will see later, C++ supports two distinctly different kinds of polymorphism.  One kind of polymorphism exists when functions or operators are overloaded to cause them to perform operations not inherently recognized by the compiler. One of the best-known simple examples of this kind of polymorphism is the ability to replace the three C functions shown in Figure 1 by three functions having the same name in C++.

Figure 1. Three C functions.
abs(), labs(), and fabs()

C requires three separate functions with different names to return the absolute value of the three data types. Therefore, the programmer must associate the correct name with each different type.

With C++, you can overload a single function name so that the absolute value of any of the three data types can be obtained by calling a function having the same name for each of the three data types.  In this case, the programmer only needs to remember one name, such as abs for example, and the compiler makes the decision as to which specific function will be executed.  The decision is based on the type of data to which the function is applied.

Overloading a plus operator

Another good example of polymorphism is the overloading of the plus operator for a coordinate class so that when used in the context of that class, the operator will cause the addition of two sets of coordinates in a multi-dimensional system of coordinates.

Not new to C++

Polymorphism is not necessarily new to C++. For many years, for example, some programming languages have interpreted the plus operator to be an arithmetic operator when applied to numeric data and to be a concatenation operator when applied to string or character data. That is clearly an example of polymorphism. However, in C, you could not invent new polymorphic behavior that was not already intrinsic to the language.  C++ provides the capability for you to customize and extend polymorphic behavior into areas not supported as an intrinsic capability of the compiler.

Virtual member functions

Polymorphism also exists when a derived class customizes the behavior of the base class to meet the requirements of the derived class. A C++ base class uses the virtual member function declaration to specify that overridden member functions in derived classes have polymorphic behavior with respect to that method.

Compile-time polymorphism

The implementation of compile-time polymorphism consists of defining multiple functions with the same name (but different formal argument lists), and having the compiler call the correct function at the appropriate point in the execution of the program depending on the type and/or number of parameters in the actual parameter list.

If the construction is such that it is clear at compile time which function should be called, this is referred to as early binding, static binding, or compile-time polymorphism.

Run-time polymorphism

It is also possible to invoke a virtual function through a pointer in such a way that it is not known at compile time which version of the function is to be called. That determination cannot be made until the program is actually executed. This is often referred to as late binding, dynamic binding or run-time polymorphism

Summary

The object-oriented programming paradigm is a rich environment for the expression of the data formats and functions for an application. Unlike other languages such as Java and C#, it is not necessary for C++ programmers to immerse themselves totally in OOP. C++ has the facility to support the basics of OOP while permitting the programmer to use traditional procedural programming where it seems appropriate.

C++ provides a number of improvements over C even if you never dip your toe in the OOP water and define an abstract data type.

Resources


Copyright

Copyright 2008, Richard G. Baldwin.  Reproduction in whole or in part in any form or medium without express written permission from Richard Baldwin is prohibited.

About the author

Richard Baldwin is a college professor (at Austin Community College in Austin, TX) and private consultant whose primary focus is a combination of Java, C#, and XML. In addition to the many platform and/or language independent benefits of Java and C# applications, he believes that a combination of Java, C#, and XML will become the primary driving force in the delivery of structured information on the Web.

Richard has participated in numerous consulting projects and he frequently provides onsite training at the high-tech companies located in and around Austin, Texas.  He is the author of Baldwin's Programming Tutorials, which have gained a worldwide following among experienced and aspiring programmers. He has also published articles in JavaPro magazine.

In addition to his programming expertise, Richard has many years of practical experience in Digital Signal Processing (DSP).  His first job after he earned his Bachelor's degree was doing DSP in the Seismic Research Department of Texas Instruments.  (TI is still a world leader in DSP.)  In the following years, he applied his programming and DSP expertise to other interesting areas including sonar and underwater acoustics.

Richard holds an MSEE degree from Southern Methodist University and has many years of experience in the application of computer technology to real-world problems.

Baldwin@DickBaldwin.com

-end-