Classes in C++

If you’ve been following this blog, then you know I just wrote an article about passing objects as arguments in C++.

This article was originally intended to be part of that one. But while writing it, I realized that you need to know about objects in general, and how they work in C++. This is not a trivial subject, and what started out as a mere introduction ballooned into the article you see before you.

This article will cover object life cycles, inheritance, polymorphism, and composition. These are deep subjects in their own right, so I’ll only cover how C++ handles them. Even so, it is not an article for people who are completely new to object-oriented programming. It will require lot of thought, but in my opinion, it’s worth it. Aside from being a better C++ programmer, you will also gain a deeper understanding of how other languages operate “behind the scenes.”

So, let’s start at the beginning. We need to know what a class actually is, and how they are handled in C++.

C++ Classes

A class is a piece of code that specifies a type. Classes are like blueprints; the term “class” does not refer to any actual object in memory. When you create an object of a class type, you instantiate that class. For this reason, objects are referred to as instances of a specific class type.

A class can include variables and/or functions. In C++, these are called data members and member functions. (The term class member is a blanket term for both.) Most other object-oriented languages call member variables instance variables or fields, and member functions methods. When you call a member function, you invoke a method.

Some object-oriented languages call member functions messages, reflecting the idea that calling a member function is “passing a message” to an object. This terminology reflects the idea that an object is an agent, not just a bundle of data and functions. For more about this topic, I highly recommend that you read Object-Oriented Programming with Objective-C (PDF) from the Apple developer library. Despite the name, it is entirely about object-oriented programming, and does not contain a lick of Objective-C code; it is also a quick read.

Classes in C++ are a language extension of C’s struct data type (which, in turn, inherited this type from assembly language). In C, it was possible to do a kind of quasi-object-oriented programming by using typedef struct and function pointers. This is a total pain, but it is occasionally still done by C programmers. C++ still has struct types, but in C++, a struct can also have member functions, making them nearly identical to C++ classes.

C++ classes also inherit the same operators that you would use on a C-style struct. To refer to a member of a class, you would use the member access operator, often called the “dot operator” because it is represented by a period: “.“. Here’s an example:

obj.someField;

If you tried to use this operator with a pointer, you would first have to dereference that pointer; but the dereference operator has a lower precedence than the member access operator, so parentheses would be required. This syntax is unwieldy, so C (thus C++) has a second member access operator for pointers, often called the “arrow operator” because it looks like an arrow: “->“.

The following two lines of code are exactly equivalent:

(*ptr).someField;
ptr->someField;

C++ also inherited C’s distinction between declaration and definition. A class’s declaration is usually done in a header file, having a .h extension. The declaration includes data member declarations and prototypes for member functions, but does not include code that implements the member functions, or assigns values to data members. This is done in a class’s definition, which is usually in a C++ file having the same name as the header file, but with a .cpp extension. This is so that the implementation file can be compiled and linked separately.

Exceptions can be made for member functions with very simple implementations (like getters and setters). Definitions for these are often placed in the header file. But, be forewarned: the compiler will inline any function defined in the header file. Behind the scenes, the compiler simply replaces all calls to that member function, with the actual code in its implementation. (It’s similar to how macros work in C.) Needless to say, if the member function contains more than one or two lines of code, this can be very inefficient.

New to C++ is the scope resolution operator. It is represented by two colons next to each other: “::“. This operator is used to specify the scope of a function or variable. It is used with both classes and namespaces.

A C++ namespace is roughly equivalent to a Java package: it “bundles up” a slew of different functions, classes, or objects. Unless you’ve never written a line of C++ in your life, you’ve used the std namespace, the one used by the C++ standard library. If you’re at the point where you’re creating your own namespace, you probably know everything in this article already.

You have to use the scope resolution operator when you are writing the definition of a member function. If you simply put the name of the function you are implementing, the compiler will believe that you are declaring a stand-alone function (one that is not a member of any class). To declare it a member function of a class, you resolve the function to the scope of that class, like so:

// Implement the "someMethod" member function of the "MyClass" class
void MyClass::SomeMethod(void)
{
    // ...code goes here...
}

Generally speaking, you don’t use the scope resolution operator when you are calling a member function. You just use the dot operator or arrow operator as appropriate. The exception is when you are calling a static member function. Static member functions – like static variables – are placed into static memory, and are not bound to any particular object. You usually call a static member function without ever instantiating any object at all. But to do this, you need to resolve the static function to the scope of the class that has it as a member. Here’s an example:

int x = SomeClass::someStaticFunction();

This is different from some other object-oriented languages, which combine the member access operator and the scope resolution operator. For example, Java uses the dot operator for both.

Object Life Cycles

The life cycle of an object starts with an object’s creation (when memory is allocated for it), and ends with its destruction (when that memory is freed). In this section, I will also talk about initialization and assignment, since these are often done when an object is created.

An object is created using its constructor, and deleted using its destructor. The constructor is a special member function whose job is to initialize the object’s data members. The constructor accepts parameters, like any other function, and these parameters are used as the initial values for the data members. It should also do any other work needed when the object is allocated memory.

To define a constructor in C++, you create a member function with the exact same name as the class itself, and no return value. Like other C++ functions, you can have multiple overloaded constructors, and their parameters can have default values. If all a constructor’s parameters have default values, it overrides the no-argument constructor.

C++ constructors also have initialization lists. As the name implies, an initialization list is a list of a class’s data members, initialized to a parameter of the constructor. Initialization lists are placed in the constructor’s definition, between the constructor’s signature and its body. To define an initialization list, place a colon after the constructor’s signature, followed by a comma-separated list of data member initializations. Each initialization uses implicit assignment syntax: the data member name, with an initial value (a constructor parameter) in parentheses. I will explain why initialization lists are necessary later in the article.

Destructors are similar, but they should do whatever work is needed when the object is freed from memory. Destructors are often used for their side effects: deleting composed objects created on the heap, closing database connections, freeing resources, and so forth. To define a destructor in C++, you create a member function whose name is a tilde (“~”) followed by the class name, with no return value. Since they do not initialize data members, destructors do not accept any parameters. Destructors cannot be overloaded.

So far, this should be at least somewhat familiar to people who know another object-oriented language. But there are a number of major differences between object life cycles in C++, and object life cycles in other languages. Here’s a summary:

  • C++ allows objects to be created on either the stack or the heap.
  • Objects created on the heap must be explicitly destroyed; objects created on the stack do not.
  • Declaring an automatic variable of a class type immediately constructs an object on the stack.
  • Objects created on the heap must be assigned to a pointer.
  • Initialization and assignment are different operations.

To examine these differences in detail, let’s look at how the same code would be interpreted in Java and C++. Let’s say we have a custom class called MyClass, whose only constructor takes a string literal as its sole argument.

Here’s how we could create an object of that class in Java:

MyClass obj;
obj = new MyClass("Hello, world!");

But this same code will not compile in C++. To understand why, you have to understand what the Java code is actually doing.

The first line is creating a reference to a MyClass object. This is a Java reference, which is really a “simulated reference” – that is, a pointer. The second line is creating a new MyClass object on the heap, then assigning a reference to that object to the obj variable.

But this is not how C++ would interpret this code. In C++, declaring an automatic variable immediately constructs the object on the stack. If no constructor is specified, C++ will construct the object using the class’s no-argument constructor. If you don’t declare a constructor for your class, the C++ compiler will automatically generate a default, no-argument constructor for you. However, if you do declare a constructor – with or without arguments – the compiler will not generate a default constructor.

This is why the first line won’t compile. You are not simply declaring a reference, you are attempting to create an object on the stack – with a no-argument constructor that doesn’t exist. If we intend to create an automatic variable of the MyClass type, we need to do this:

MyClass obj("Hello, world!");

Incidentally, this syntax also works with primitive types in C++. It may look weird, but this is perfectly valid C++ syntax:

int myInt(5);

This is the syntax used in a constructor’s initialization list.

It is very common to create objects using automatic variables. For many C++ programs, it might be all you need. But suppose we want obj to be created on the heap instead. How do we do that? The answer is, we can’t – at least not if we want to keep obj as an instance of the MyClass type. This is why the second line in our Java code would not compile in C++.

In C++, we can only create pointers to objects on the heap. We do this using the new operator, which constructs the object on the heap, then returns its memory address. Most other languages (including Java) use reference variables to hide the fact that they are using pointers behind the scenes. But with C++, we must be explicit, and use pointer syntax.

There is, of course, a catch. Unlike most other object-oriented languages, C++ does not manage heap memory. Whenever you create an object on the heap, you must explicitly delete it. If you don’t, you will get a memory leak: a chunk of memory that is allocated but never freed, leaving it unavailable during the program’s execution. (In old OS’s, a memory leak would stick around even after the program closed.) You do not need to delete objects represented by automatic variables; these will be deleted as the stack unwinds.

Here’s the C++ equivalent of the Java code:

MyClass* obj;
obj = new MyClass("Hello, world!");
// ...do stuff with obj...
delete obj;

When an object is deleted – either on the stack, or on the heap – that object’s destructor is called.

There is one final subject we need to cover before we finish with object life cycles. In many languages, there is no difference between initialization and assignment. That is not true with C++. Consider this simple C++ code:

MyClass objOne = objTwo;
objOne = objTwo;

These two lines may look like they’re doing the same thing, but they’re not. The first line is initializing objOne with objTwo, and the second is assigning objTwo to objOne. They are different because initialization happens during creation. Initialization uses the copy constructor: a constructor that accepts another object of the same type as its parameter. Assignment operations use the assignment operator for that class.

In other words, there is absolutely no difference between these two lines of C++ code:

MyClass objOne = objTwo;
MyClass objOne(objTwo);

The fact that initialization and assignment are different doesn’t just apply to classes, of course. C++ references cannot be assigned to reference different variables after they are initialized; and constant variables can’t be assigned anything after they’re initialized.

This is one reason that C++ has initialization lists for constructors. By the time the constructor’s body is entered, the object already exists in memory, and its data members have already been initialized. Without an initialization list, it would be impossible to initialize references or constant data members. Additionally, any data members that are themselves objects would have already been constructed; it would be impossible to explicitly call any of their constructors, and you’d be limited to assignment operations. For a more detailed explanation, read the constructor initialization lists tutorial on LearnCpp.com.

Since initialization is semantically the same as copy construction, you can get behavior that you might not expect. For example, this code will compile:

string str = "Hello, world!";
MyClass objOne = str;

When this happens, C++ does an implicit type conversion. The string on the right of the assignment operator is used to call MyClass’s constructor that accepts a string argument. The second line is semantically equivalent to this:

MyClass objOne(str);

If you don’t want this behavior, then you can declare the MyClass constructor to be explicit. This tells the compiler not to do any implicit type conversion.

Note that assignment does not do implicit type conversion. To make assignment work in our example, you must explicitly create an assignment operator that accepts a string argument.

If you don’t declare a copy constructor or an assignment operator, then the C++ compiler will generate defaults as needed. They each do a bitwise copy of the other object; they simply take what’s in the other object’s allocated memory space, and copy it bit-by-bit into its own memory space. This is also called a shallow copy.

Destruction, copying, and assignment can often cause hard-to-find bugs, especially when composition or inheritance are involved.

Inheritance

Inheritance means that one class can inherit the data members (fields) and member functions (methods) of another class. The class that is inherited is called a superclass or base class, and the class that does the inheriting is called a subclass or derived class. Other languages use the terms parent class for superclass, and child class for subclass. These are all relative terms; a class may be a subclass of one class, and a superclass of another. A class that has subclasses but no superclass is called a root class, and one that cannot have any subclasses is called a leaf class. (This comes from picturing inheritance as a kind of tree.)

To make sure inherited variables are initialized, subclasses will always call the constructor of its superclass. If this is not done explicitly in code, then its no-argument constructor is called. This can sometimes cause issues. Remember, if a constructor is declared with any arguments, then the compiler won’t generate the default constructor. If this is the case, the code won’t compile unless the subclass explicitly calls the superclass’s constructor.

Like other languages, C++ allows access modifiers for class members. To make a member available to nobody but the class itself (including subclasses), a class can declare that member private. To make a member accessible to its subclasses, but not to any other class or to client code, a class can declare that member protected. To make a member available to everything, a class can declare that member public.

The syntax for access modification is different in C++ than in most other languages. The C++ syntax specifies that an access modifier is persistent, modifying all members that follow it; member access is only changed when another access modifier is specified. Thus, all public, protected, and private members can be grouped together. The access modifier is followed by a colon, and by convention, it is placed on a line by itself, and dedented (“indented to the left”) from the class members.

If no access modifier is declared, the compiler assumes a class member is private. This is unlike a struct; by default, a member of a struct is public. This is the only difference between a class and a struct in C++.

Also like other languages, C++ allows subclasses to override the member functions of a superclass. This means that a subclass can re-implement a member function of its superclass. But unlike other languages, C++ does not allow this automatically. To allow a subclass to override a member function, that member function must be declared virtual. Shockingly, this is done by placing the virtual keyword before the function signature.

You only have to declare member functions to be virtual; public or protected data members are automatically inherited.

Often, a superclass will be used only to define the interface (the member functions and their signatures) of its subclasses. In other words, the superclass would not implement those methods at all. To do this, a member function may be declared pure virtual. This is done by placing = 0 after the function signature, in addition to using the virtual keyword.

A class with one or more pure virtual member functions is called an abstract class. A class that is not abstract is a concrete class. An abstract class with nothing but pure virtual member functions is called a pure virtual class. You don’t explicitly declare classes to be abstract or pure virtual; this is determined by the compiler. You can only instantiate objects of a concrete class type.

Unlike most other languages, C++ allows multiple inheritance. This is when a subclass has more than one superclass. Multiple inheritance can cause problems due to naming conflicts. For example, let’s say class A has a member function called toString(), and class B also has a member function called toString(). Now, a subclass – say, class C – has both of these classes as superclasses, and does not itself implement the toString() function. Which version of toString() would it inherit? There’s no way to tell, so the code won’t compile. A common scenario that causes this problem is the “deadly diamond of death.”

To get around this, C++ has virtual inheritance. To use virtual inheritance, place the virtual keyword before the class that you are inheriting. This will signify to the compiler that all ambiguous members will resolve to the (common) superclass. It is the “intermediate” classes that must use virtual inheritance; in our example, it would be classes B and C.

But you shouldn’t need virtual inheritance. In a well-designed class heirarchy, a C++ class has at most one abstract or concrete superclass, and any other superclasses are pure virtual classes. This is precisely what Java does with its interface types. In C++, this is a design choice, not a language requirement.

Polymorphism

Polymorphism is the general term for functions that have the same names but different implementations, depending upon type. When most object-oreinted programmers say “polymorphism,” they mean subtype polymorphism – polymorphism arising from object inheritance. If you’re curious, the other kinds of polymorphism are parametric polymorphism (implemented in C++ using templates), and ad hoc polymorphism (function or operator overloading). When I say “polymorphism,” I am referring only to subtype polymorphism.

Polymorphism means that a variable declared to be a superclass type, may refer to an object in memory that is actually a subclass of that type. If you invoke a virtual method using this variable, it will use the subclass’s implementation, and not the superclass’s implementation. In C++, this variable must be declared as a pointer or reference to the superclass type.

An example will probably make this clear. Let’s say we have a class called Base, with a virtual member function called toString. We also have a class called Derived, which overrides the toString member function. Here’s how it would work:

Base* ptr;
ptr = new Derived();
cout << ptr->toString();
delete ptr;

Even though ptr is declared as a pointer to the superclass (Base), the member function that is actually called is the toString function from its subclass (Derived).

Remember that for polymorphism to work, the member function must be explicitly declared virtual. This includes destructors. The destructor for a subclass is not automatically called unless the superclass’s destructor is virtual. This is by design, so your code will compile just fine – but running it will lead to undefined behavior. Most compilers simply won’t execute the subclass’s destructor, and if it’s deleting heap-created objects, this will cause memory leaks.

This does not apply to constructors, however; some superclass constructor is always called, so they don’t need to be declared virtual. But this fact can also lead to hard-to-find bugs. Consider the following class:

class Base
{
    Base() { doStuff(); }
    virtual void doStuff() { cout << "Base!" << endl; }
};
class Derived
{
    virtual void doStuff() { cout << "Derived!" << endl; }
};
// in main()
Derived d;

What happens when the Derived object is created? Obviously, the Base constructor is called. But which version of doStuff is used?

It may surprise you to learn that “Base!” will be printed out. Virtual function calls in base classes are treated as if they are not virtual. When the Base class is constructed, it is still using the vtable for the Base class; it is only after the Derived constructor is called that the object’s hidden pointer references the vtable for the Derived class. If this function was pure virtual, then there would be no base class implementation, and you couldn’t even construct the object properly. This leads to “undefined behavior” in C++; most compilers will generate a warning, but even if not, the linker will fail. This behavior isn’t limited to constructors, either; if the base class destructor is called, it will be called after the derived class’s destructor, and behave the same way.

This leads to the following rule: never call virtual methods in a constructor or destructor. For a more in-depth look at this topic, see this exerpt from Effective C++ by Scott Meyers.

This is another reason that you need an initialization list in the constructor. If you have a subclass constructor, the superclass constructor is called before you even enter the body of the subclass constructor. If the subclass constructor has parameters that initialize data members in the superclass, you need to call the superclass constructor in the initialization list. For more on this topic, read Understanding Initialization Lists in C++ by Alex Allain.

It is important to note that instances are not subtype polymorphic. Polymorphism works with pointers and references because they are not actually copies of the object. Instead, they “re-route” any virtual function calls to the object itself.

The term for this is late binding. It may be helpful to understand how this is implemented at the machine level.

Behind the scenes, subtype polymorphism is achieved using a virtual table, or vtable for short. This is essentially a lookup table for virtual functions. Whenever any class has a virtual function, the compiler sets up a unique table for that class, and stores it in the program’s executable. The function signature for every virtual function is paired with the memory location of the code that implements it. You can think of it as a hash map, whose key is the function signature, and whose value is a function pointer.

With a base class, this is pretty straightforward. The vtable maps the virtual function to the implementation in the base class. If those functions are pure virtual, the pointer to its implementation would be a null pointer (or the machine language equivalent).

With a derived class, it depends upon whether the derived class overrides the base class implementation. By default, the vtable entry points to the base class implementation; this is how it is inherited. However, if that function is overridden, then the vtable entry points to the implementation in the derived class.

When an object is created, the machine code generates a hidden pointer to its class’s vtable; this is called the “v-pointer,” usually abbreviated vptr. This is bound to the object itself, and can never be changed. It is not copied when the object itself is copied; it is permanently fixed as soon as the object’s type is determined.

But let’s examine what happens when we invoke a virtual method on a pointer. The pointer is dereferenced to the object it points to. That object has vptr, and that vptr is dereferenced at runtime. If the object is a base class type, it is dereferenced to the base class’s vtable; if it’s a derived type, it is dereferenced to the derived type’s vtable. This happens no matter what the formal type of the pointer is. The machine code then looks in the vtable to find a pointer to a function. Finally, that pointer is dereferenced, and the function is called.

Polymorphism is achieved, but obviously there is multiple indirection caused by looking through the vtable, and this has overhead. In most cases, this overhead is negligible, but it helps to be aware of it.

Remember, this only happens when you invoke a virtual method on a pointer. Non-virtual methods are not handled this way; they are resolved statically – at compile time, not runtime. For details, read questions 20.3 and 20.4 from the C++ FAQ. As an aside, even if a function is virtual, the default values for its parameters are resolved statically, leading to some, um, “interesting” behavior. For more on this, read GotW #5 by Herb Sutter.

C++ references are also polymorphic. Since a reference is an alias for another variable, its actual type will be the type of the variable that it references. You can initialize a base class reference with an object of a derived class, and that reference will use the derived class’s member functions.

This is how inheritance works with member functions (methods), but not how it works with data members (fields). When a base class is instantiated, enough memory is allocated to hold its fields. When a sublcass is instantiated, memory is allocated for the fields inherited from the base class, and then additional memory is allocated for its own fields. This is why inherited fields do not need to be declared virtual.

However, this does create problems when multiple inheritance is involved. Consider the “diamond of death” situation: base class A is inherited by classes B and C, and class D inherits from both B and C. Let’s suppose that class A has a data member named foo. Which does D inherit: B::foo or C::foo?

The answer is both. D will allocate enough memory to store both variables. To use either of them, D’s code must use the scope resolution operator to specify which inherited data member it’s refering to. If the scope resolution operator is not used, then the code is ambiguous, and will not compile. Of course, virtual inheritance will solve this problem (only the common superclass’s data member is stored). But a better solution is to re-design the class heirarchy so that you don’t inherit multiple classes with data fields.

This memory layout structure is another reason why instances are not polymorphic. Let’s look at code that uses automatic variables instead of pointers:

Base obj;
Derived copy;
obj = copy;
cout << obj.toString();

The code in the third line is copying the Derived object (copy) to the Base object (obj). This means two things:
1. The toString method will be the method from Base.
2. Any fields that are in Derived, but not in Base, will not be copied into obj.

The first happens because obj “really is” an instance of a Base class, and that object’s vptr will always point to the vtable for Base.

The second happens because the default assignment operator makes a bitwise copy of the copy object – but treats it as if it were nothing more than a Base object. So, it takes all the memory that would be allocated to a Base object, and copies it bit-by-bit into obj. Since the memory for subclass data members is allocated after the memory for base class data members, it would only copy the values of those base class data members.

This second property is known as object slicing. It can cause hard-to-find bugs, especially when passing objects by value.

Composite Objects

In all object-oriented languages, including C++, objects can contain variables that represent other objects. There are two related concepts that describe the relationship between those objects: composition and aggregation.

With aggregation, one object “uses” another object (or other objects). With composition, one object “owns” another object (or other objects). If the other objects exist independently, then you would use aggregation. If the other objects do not exist independently, then you would use composition. (In some object-oriented languages, the “used” and “owned” objects are called “extrinsic and intrinsic connections.”)

I’ll give an example. Let’s say we are modeling a car. We would have two classes, a Car class, and a Tire class. Cars have four tires, so we would have four variables inside our Car class that represent Tire objects. But is this relationship one of composition, or one of aggregation?

It depends entirely upon whether those tires are going to be used outside the context of the car itself. When we change the tires, are we simply throwing away the old ones? If so, then the tires do not exist independently of the car, and we would use composition. But let’s say we sell the used tires to someone else, who puts them on their own car. The tires exist independently of any specific car, so we would use aggregation instead.

In practice, the difference is one of life cycle management. If the class uses aggregation, its instances would not manage the life cycles of the other objects. If the class uses composition, its instances would manage the life cycles of the other objects. This distinction is not terribly important with programming languages that manage memory (like Java or C#), but it is vital in C++. Objects that use composition must create the composed objects itself (usually in its constructor), and ensure that composed objects are deleted from memory when it is itself deleted (usually in its destructor).

But no matter what, something needs to manage the memory of these objects. If you use aggregation, then the client code that created these other objects – perhaps even main() itself – has to delete them. This makes it very important to document which technique your class is using. Generally speaking, it is preferable to manage life cycles in your own class, to make sure that the client code doesn’t mess up and leave memory leaks.

Another thing to consider is that aggregation may break encapsulation. One of the strengths of object-oriented programming is that an object’s internal workings can be hidden from the client code. This is called information hiding, and it is what most object-oriented programmers refer to when they say “encapsulation.” But an aggregate object’s internal workings are not entirely hidden. If an object is an aggregation of other objects, and those objects are available to the client code, then changing those objects in the client code also changes them in the aggregate object. This is especially bad if the client code deletes those objects, yet still attempts to use the aggregate object.

For those reasons, composition is more common than aggregation in C++. Of course, this is a design decision, and that decision may not be up to you (especially if you work in a large team).

Composition isn’t such a big deal if your class is the only one that has access to the composed objects. But this usually isn’t the case, and in fact, it would make composition much less powerful than it should be. To see why, let’s go back to our car example.

Tires wear out quicker than cars, so eventualy, you’re going to need some way to replace them. And by “eventually,” what I really mean is “at runtime.” Furthermore, there are different kinds of tires: performance tires, all-terrain tires, snow tires, and so forth. In our code, we would make Tire a base class, and each different kind of tire would be a subclass. But we don’t know which specific type we want to put on our car, so the composed objects would be of the base class type. At runtime, we take advantage of polymorphism to choose which type of tires to put on our car.

With all this talk of polymorphism and memory management, you can probably guess that objects are often composed by pointer. The composing class has variables that are pointers to the base class of the composed objects. In its constructor, the composite class creates objects using the new keyword; during destruction, it calls delete on the pointers. It would do the same, but in reverse order, in whatever method is used to swap out the tires.

It isn’t possible to compose by reference. You cannot explicitly create or delete a referenced variable, so you can only use references with aggregation. Additionally, references cannot be “re-assigned” to reference other variables, and you can’t have arrays of references. For these reasons, most aggregate objects use pointers instead.

Composing by value is also used very often. (I am not sure that this is the correct term, but most people seem to understand it.) To do this, you simply declare a variable representing one specific instance, just like you would with (say) an int or double. Its life cycle is bound to the life cycle of the object that owns it; you don’t need to worry about deleting it. It acts just like an automatic variable. This makes composing by value much, much easier from a memory management perspective.

But remember, instances are not polymorphic. In the car example, our class could compose instances of Tire objects – but these could only be “generic” tires, and not snow tires, all-terrain tires, or anything else. If Tire is an abstract base class, we couldn’t compose by value at all, since you can’t create instances of abstract classes. If we need polymorphism, we must use pointers.

Inheritance vs. Composition

Composition solves most of the problems that are created by multiple inheritance. In fact, as a general rule, it is far better to use composition rather than inheritance.

The over-use of inheritance is a fairly widespread problem, and it is one that even experienced C++ programmers make (including myself, I’m sure). But it is a problem, identified by such people as the “Gang of Four” (authors of Design Patterns), the aforementioned Scott Meyers, or Herb Sutter (C++ guru and architect of C++/CLI). It even has its own Wikipedia entry.

The problem occurs when programmers use subclasses to inherit the implementation of a superclass, rather than its interface. Consider the “Diamond of Death” problem earlier: B and C inherits from A, and D inherits from both B and C. Here’s the question: why would you want D to inherit from both B and C?

The answer is invariably because B implements some method(s), C implements some other method(s), and D needs to use both their implementations of those methods. If that wasn’t the case, then D would be a subclass of only one or the other (or even A), and implement those methods itself.

But this is a bad design. If you want to use some other class’s implementation, you shouldn’t inherit from that class. Instead, you should compose that class. In our example, class D should have data members that represent objects, or pointers to objects, of types B and C.

Not only does this prevent the “Diamond of Death” problem, but it also decouples the composite object from the object that actually does the implementation. This makes code easier to maintain and update, and usually reduces compile time. It also means you can take advantage of polymorphism to change the implementation at runtime, if desired.

This doesn’t mean inheritance is useless, of course. It just means you have to use it properly. Generally speaking, you should only use inheritance to define an interface. The base class should be as close to pure virtual as the design allows. You inherit from this base class only if your subclass is going to be treated exactly as if it were this base class – and no other. For instance, its only methods should be those that are inherited from the base class. If you want your obect to do anything else whatsoever, use composition instead.

This is an example of programming to an interface. Erich Gamma (one of the “Gang of Four”) discusses it in this interview on Artima Developer.

This also solves another common problem. Let’s say your class does implement some virtual function. Unless this implementation is very simple, it’s likely that you’ll be using other member functions as “helpers.” These member functions can be private, but they will still need to be declared in the header files – available to everyone who looks. This is not the best idea, as it breaks the principle of implementation hiding.

The C++ solution to this problem is to use a “pointer to implementation,” more commonly known as the Pimpl idiom. The name was popularized by Herb Sutter; it is also called the “Handle/Body” idiom, or sometimes the “Cheshire Cat” idiom. It is an example of the Bridge pattern.

In this idiom, a concrete class inherits a set of virtual functions, but does not actually implement anything. Instead, it simply “routes” the function calls to a pointer to another class, and that other class actually implements everything.

Here’s a very simple example. Let’s say AbsBase has a single, pure virtual member function:

class AbsBase
{
    virtual void f() = 0;
};

We are writing a concrete class that will implement f using the Pimpl idiom. Here’s how it would look:

class ConcreteDerived : public AbsBase
{
public:
    void f() { impl->f(); }
private:
    class Impl; // forward declaration; not defined here
    Impl* impl; // pointer to implementation
};

The Impl class would be the class that actually implements the f member function. It would have all of the “helper” members (say, private member functions or variables) that are necessary for that implementation. But nobody who reads the header file will even know that they exist.

Note that Impl is simply a forward reference to a class. It is not defined here; you’re simply telling the compiler that a class with that name exists. Ideally, it would not be defined in any public header file. Instead, it would either be defined or imported in the .cpp file for the ConcreteDerived class. If imported, the definition would be in some location that is available to your compiler, but not shipped to clients using your library – you would only ship the pre-compiled object file.

If you are using this idiom, you would create the Impl object in the ConcreteDerived constructor, and destroy it in the ConcreteDerived destructor, just as you would for any other composed object.

Of course, this example is very simplistic, and it gets much more complicated when things like templates are involved. If you want more in-depth information, read the (optimistically named) article Making Pimpl Easy by Vladimir Batov.

The Pimpl idiom is an example of object composition, but it’s hardly the only one. You can use composition to create shared pointers, abstract data structures (like linked lists or binary search trees), and a host of other things. None of these things could be implemented using inheritance alone.

Advertisements

About Karl

I live in the Boston area, and am currently studying for a BS in Computer Science at UMass Boston. I graduated with honors with an AS in Computer Science (Transfer Option) from BHCC, acquiring a certificate in OOP along the way. I also perform experimental electronic music as Karlheinz.
This entry was posted in c++, Programming. Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s