Ali Hazzah is the president of Safe Harbor Associates, an independent consultancy which specializes in helping the MIS departments of large organizations develop applications which use emerging technologies, such as object-oriented programming and cooperative processing. Hazzah has over nine years experience in the computer industry. He may be contacted at (914) 738-5043.
Object-oriented languages support the concepts of encapsulation, inheritance, and late-binding.
These terms can be loosely defined thus:
C++ is often characterized as a multi-paradigm language, which means that it combines non-object-oriented concepts with object-oriented ones.
- Encapsulation implies the explicit grouping of data and one or more functions into a single programming construct, with the data accessible only through its predefined function set.
- Inheritance involves the "reuse" of preexisting chunks of software to build new functionality (which automatically "inherits" the charateristics of the reused code) into a program.
- Binding refers to the attachment of member functions to an instance of a class, or of function calls to those functions with which they are associated. It is an evaluative process which may occur at compile time or at runtime. When binding occurs at runtime, the process is called late-binding.
The question is, to what degree is C++ object-oriented?
A fundamental notion in C++ is that of a type. Types can be variables, structures, or classes. A variable type may be a character, integer, floating point number, or a double.
A variable can be declared in a program as follows:
int x;where x is the variable name, and int its type.Variables can be grouped together into structures. These constructs help to organize data in a program by treating it as a unit. For example:
struct point { int x; int y; };point is the structure tag; x and y are its members.A structure may only contain variables.
In C++, operators such as + or - are used to manipulate variables. A programmer may write an expression using these variables and operators to produce a new value. For example:
x + y ...here, the variables x and y are added, presumably to arrive at some meaningful result.Since x and y have previously been declared with the keyword int (which is provided by C++), the programmer need not be concerned with internally representing these variables. In other words, the compiler is charged with defining their representation at the machine level.
Moreover, the programmer need not worry about specifying to the compiler how integer addition is to performed; this job is also taken care of by the compiler.
C++ provides the programmer with a number of built-in variable types. In addition, C++ allows the compiler's type support mechanism to be extended by allowing the programmer to define types.
Programmer-defined types are implemented as a combination of variables and structures, as well as the permitted operations on these. The operations themselves are implemented as functions which are written by the programmer. C++ provides the type class for this purpose.
A class lets the programmer gather, in a self-contained unit recognized by the compiler, the representational and operational aspects of the new type. It also provides mechanisms for specifying various levels of accessibility to the data and functions contained within the class. For example:
class shape { // the default is private point center; void private_move (point to); protected: void protected_move (point to); public: void move (point to); ... };where shape is a class containing a private variable center, the private function private_move, the protected function protected_move, and a public function move.In C++, this technique implements encapsulation, since it places center and its associated functions into a "capsule." All accesses to center must take place through these functions; this rule is strictly enforced by the compiler.
Encapsulation
A C++ programmer begins to write a program by building a conceptual framework of the problem domain.The programmer identifies various concepts, decides on how to represent these, and specifies the operations which can be performed on instances of that concept.
An instance of a class is called an object and is declared as:
shape myshape;This statement declares myshape as a variable of the class shape.A client (any external function) typically interacts with an object by issuing function calls. For example:
myshape.move (point (0,2));which moves myshape from wherever it was to the location 0,2.Hiding the implementation from the client allows the programmer to safely modify a member function. As long as its calling sequences and semantics are preserved, client calls will continue to work, since encapsulation prevents them from relying on particular implementation details.
In addition, encapsulation adds to program understandability, promotes reusability, and simplifies debugging (providing that source level debuggers are used).
Inheritance
Inheritance takes C++ a step further into "object-orientedness."An overall design principle in C++ holds that a concept does not live in isolation, but is related to other concepts. This principle is supported in C++ by inheritance, which is basically a compiler-enforced organizational notion.
In effect, a programmer designing a C++ application first needs to identify and understand the concepts which are required by the application, in order to create hierarchical classes out of these.
A class hierarchy in C++ is composed of a base class and its derived classes. A base class defines a fundamental notion, which a derived class then refines by providing additional members. This mechanism allows derived classes to reuse the existing capabilities of the base class, and modify only what is needed: the base class is simply extended so that the programmer does not have to rewrite it in order to add new features.
For example, a problem domain may contain, in addition to the general concept of shape, the slightly less general concepts of circle and triangle. The concepts of circle and triangle are related, in the sense that they are both shapes.
A programmer may choose to indicate these relationships through the mechanism of derived classes:
class circle : shape { void foo1(); }; class triangle : shape { void protected: foo2(); };where foo1 and foo2 represent the unique members of circle and triangle respectively.In C++, a class may have public members (which are available to clients and derived classes), protected members (which are available only to derived classes), and private members (which are only available to the implementing class itself). For example:
void newfunc(circle *p, triangle *q){ p->foo1() // error: circle::foo1 is private ... q->foo2() // error: triangle::foo2 is protected ... };where newfunc is a standalone function.However, derived classes may access public or protected members of their base class:
void circle::foo1() { protected_move( point (0,2)); // this is ok ... };C++ also provides the keyword friend to designate functions which are allowed access to private class members, but are themselves non-member functions:
class shape { color col; ... public: ... friend void myfriend(shape *); ... };which defines a friend function myfriend, so that:
void myfriend(shape * p){ ... p->col = red; // this too is fine ... };C++ supports multiple inheritance, which is the ability of a derived class to inherit the attributes of more than one base class. The judicious use of multiple inheritance may further increase code reusability and program flexibility.For example, the programmer may wish to create a class which displays text on a shape:
class composite_class : circle, display txt { ... };where the derived class composite_class inherits attributes from the base class circle and a class display_ txt.Like most flexible techniques, single or multiple inheritance can be quite powerful when used skillfully, but can lead to messy programs when used improperly.
As a rule, programmers must ensure that base class functions have generalized functionality (to prevent unexpected behaviors in the context of their derived classes), and that each derived class be provided with its own implementation of the base function.
Polymorphism
To achieve inheritance in an elegant manner, C++ supports the concept of polymorphism.Polymorphism allows a programmer to have the same interface to different objects: a consistent interface will produce different results, depending on the type of the object.
Polymorphism is intimately associated with late-binding.
In C++, polymorphism is implemented as the combination of virtual functions and derived classes:
class shape { ... public: ... virtual void draw (); ... };which declares a virtual function draw.In this example, the programmer specifies that a shape be associated with a draw function, but not how that function will be implemented.
This stands to reason, since there are many classes of shapes, each requiring different implementations of the draw function. These derived classes will therefore implement the behavior as part of their own methods.
During execution, the runtime environment decides which function to invoke. The decision is based on the object type. For example:
class circle : public shape { void draw (); ... }; class triangle : public shape { void draw (); ... }; // two classes derived from shape void myfunc (shape *p){ p->draw(); ... }; // a function which draws any shape void anotherfunc () { myfunc (new circle); myfunc (new triangle); }; // new makes an object on the heapPolymorphism should not be confused with function overloading, which lets the compiler determine at compile time the invocation on the basis of the function argument types.
class display_txt { ... public: ... void display_func(int); // display an integer void display_func{char*);// display a string ... };which declares the argument type of display_func to be an integer or a string, and leads to:
display_txt t; // declare t t.display_func (1); t.display_func ("hello");where the compiler ensures, prior to execution, that the correct version of the display function is called.As a rule of thumb, the later the bind, the greater the flexibility of a program. The price for this flexibility in many object-oriented implementations has been performance.
However, a number of studies have shown that the differences in execution speed in C++ between a polymorphic call and a statically-bound call is quite minimal.
Conclusion
C++ fundamentally supports the concepts of object-oriented programming encapsulation, inheritance, and late-binding. It also gives programmers who wish to program at a low-level the option to do so.Moreover, C++ is widely available. There are numerous commercial implementations of C++ compilers, debuggers, linkers, and other programming tools. The language itself, as is implied by its name, is compatible with existing languages (in particular, with C) and operating systems.
Although an ANSI committee has been formed X3J16 to begin the process of standardizing C++, the current de facto standard remains AT&T's translator, formally known as the C++ Language System Release 2.0 for UNIX System V.
This translator does not currently support a number of features, such as automatic storage management (which is available, however, from other sources as an add-on facility), double dispatching, before and after methods (i.e., functions), and parametrized typing (sometimes called genericity), which are associated with richer and slower object-oriented language implementations.
Still, C++ is an evolving language. It can be expected to provide selective degrees of support for these (and other) features over time, with genericity and error-handling being the most likely additions in the next major release of the official translator.
The author wishes to thank the inventor of C++, Bjarne Stroustrup of Bell Labs, for his cooperation.