Home > C++ > C++ Class Hierarchies Crash Course: Derivation (“is-a”) vs Composition (“has-a”), and Is a Square a Rectangle?

C++ Class Hierarchies Crash Course: Derivation (“is-a”) vs Composition (“has-a”), and Is a Square a Rectangle?


In this article I’m going to rattle over some basic design choice dilemmas you might face when trying to organize classes into a hierarchy. We’ll look at standard derivation (the “is-a” relationship), composition (the “has-a” relationship), when to use which, and also touch briefly on interfaces and private inheritance.

Pre-requisites you should know before reading:

  • how to define a class in C++
  • what a constructor is and how to define one
  • the difference between public and private members

The “Is-a” Relationship

Is a square a type of rectangle? Is a rectangle a type of square? Or is a square merely a rectangle with a certain set of properties (identical width and height)? Are circles and rectangles both types of shape, or are they even related at all? The answers to these questions have nothing to do with your natural intuition or experience from the real world (although that helps sometimes), but rather, what you are planning to do with them in your code.

Possible implementations:

1. A square is a type of rectangle
class Rectangle {
  double width;
  double height;

public:
  Rectangle(double w, double h) : width(w), height(h) {}

  double GetArea() { return width * height; }
};

class Square : public Rectangle
{
public:
  Square(double sideLength) : Rectangle(sideLength, sideLength) {}

  // Functions that can be useful for squares but not rectangles
};

The Cafes Programming presents a compelling argument for why a Square Is Not a Rectangle.

Consider if we had added some property setters to Rectangle:

void SetWidth(double w) { width = w; }
void SetHeight(double h) { height = h; }

(property setters are just methods which set the value of a private member as above)

In the original code, our Square class does not violate the so-called contract of the Rectangle class (the list of roles it is expect to fulfil, in simple terms), because the length of each side can only be set by the class constructor, not a property setter. However, once we add the property setters, you can now change the width and height of a Square separately, and it could end up being a rectangle, which makes no sense. See the referenced article for more details.

Containers (ie. arrays and STL containers) are a problem too: what if you want a container for which it is automatically enforced that it can only store squares? If we don’t derive a Square from a Rectangle and use Rectangle to store rectangles and squares alike, we can only use a construct like:

std::vector<Rectangle> rectangleList;

which fails to enforce the condition that all sides are of equal length.

On the other hand, the inheritance of Square from Rectangle causes Square to have an extraneous and unnecessary property, so you fail to get an ideal solution in either case. Perhaps then it is preferable to derive Rectangle from Square instead?

2. A rectangle is a type of square
class Square {
  double sideLength;

public:
  Square(double sideLength) : sideLength(sideLength) {}

  virtual double GetArea() { return sideLength * sideLength; }
};

class Rectangle : public Square
{
  double height;

public:
  Rectangle(double width, double height) : Square(width), height(height) {}

  double GetArea() { return sideLength * height; }
};

This goes against our natural intuition (and what we are taught by mathematics) that a square is a rectangle with a constraint on the length of each side. Also it is very counter-intuitive (and harder to read the code) to re-purpose sideLength in the derived class as the rectangle’s width. We shouldn’t change the meaning of a property when we derive a class. This solution also doesn’t prevent the issue of having containers which can only store squares, because when the container is a list of pointers or references, Rectangle can be upcast to a pointer or reference to Square.

The Cutting Ledge’s article A Square is a Rectangle presents another interesting take on the problem.

3. A square is a rectangle with specific properties
class Rectangle {
  double width;
  double height;

public:
  Rectangle(double s) : width(s), height(s) {}
  Rectangle(double w, double h) : width(w), height(h) {}

  double GetArea() { return width * height; }

  bool IsSquare() { return (width == height); }
};

This is a little more satisfying but not great. We can create a square by passing one side length to the constructor, or a rectangle by passing two, which is nice. Unfortunately the class has to know about the possibility that it might be a square, and still stores an unnecessary extra property when it is.

Perhaps squares and rectangles aren’t related after all?

4. Separate non-derived classes
class Rectangle {
  double width;
  double height;

public:
  Rectangle(double w, double h) : width(w), height(h) {}

  double GetArea() { return width * height; }
};

class Square {
  double sideLength;

public:
  Square(double sideLength) : sideLength(sideLength) {}

  double GetArea() { return sideLength * sideLength; }
};

This solves all the previous problems of extra properties, constrained container types and so on, but now we lose the polymorphism available in the the first two solutions by being able to call GetArea() indiscriminately on any element in a heterogeneous container of Rectangle pointers or references, regardless of the actual object type.

5. Define an interface

Instead of deriving one shape from the other, we can use the code from implementation 4 above and relate the classes by using an interface clsss (this will be familiar to you if you program in C# or Java):

class IArea {
public:
  virtual ~IArea() {};

  virtual double GetArea() = 0;
};

class Rectangle : public IArea {
  double width;
  double height;

public:
  Rectangle(double w, double h) : width(w), height(h) {}

  virtual double GetArea() { return width * height; }
};

class Square : public IArea {
  double sideLength;

public:
  Square(double sideLength) : sideLength(sideLength) {}

  virtual double GetArea() { return sideLength * sideLength; }
};

If you’re not familiar with the concept, an interface is a class which contains only abstract members (known as pure virtual members in C++). These are defined by simply providing a (virtual-prefixed) method signature and adding = 0 at the end instead of a method body. Pure virtual members must always be implemented by any class which derives from the interface. Thanks to C++’s support for multiple inheritance, one class can be forced to implement multiple interfaces without problems. Note that the interface should include a virtual destructor so that if the programmer tries to delete an object of the interface type, the derived object’s destructor gets called.

Now we can store squares and rectangles heterogenously in a container of IArea objects, bringing back the polymorphism we lost in the previous example. We can enforce containers with only Squares or only Rectangles. There are no extra properties stored for the square, and if we want to force all the shape classes to implement additional functionality (for example, calculating the perimeter), we can just add pure virtual methods (known as abstract methods in other languages) to the interface class. Problem solved!

The “Has-a” Relationship

When a type of object can be interpreted as an extended version of another type of object (eg. a plane is a type of vehicle), we use derivation to implement the is-a relationship. When a type of object has another object as a part of it (for example, a car might have wheels and a gearbox), we use composition to implement the so-called has-a relationship.

Composition is merely the act of including another class object as a member of the class you are defining. Here is a car with 4 wheels and a gearbox:

class Car {
  Wheel wheels[4];
  Gearbox gearbox;
};

A car is not a type of wheel, or a type of gearbox, but it has both as its components, so we include them as members in the type definition (this is composition).

The car is a obvious example but sometimes it can be harder to discern when one should use derivation or composition. A thermostat certainly has a thermometer as part of its machinery, but is it a type of thermometer? Or is it a type of machine to control temperature, which has a thermometer as a component?

A thermostat is a type of thermometer:

class Thermometer {
protected:
  float temperature;

  void updateTemperature() { /* some code to measure the temperature from a physical sensor, sets 'temperature' */ }

public:
  float GetTemperature() { updateTemperature(); return temperature; }
};

class Thermostat : public Thermometer {
private:
  float targetTemperature;

  void incTemperature() { /* do something to increase room temperature */ }
  void decTemperature() { /* do something to decrease room temperature */ }

public:
  Thermostat(float target) : targetTemperature(target) {}

  bool ControlTemperature()
  {
    updateTemperature();

    if (temperature < targetTemperature)       incTemperature();     if (temperature > targetTemperature)
      decTemperature();

    return (temperature == targetTemperature);
};

A thermostat has a thermometer as a component:

class Thermometer {
private:
  float temperature;

  void updateTemperature() { /* some code to measure the temperature from a physical sensor, sets 'temperature' */ }

public:
  float GetTemperature() { updateTemperature(); return temperature; }
};

class Thermostat {
private:
  Thermometer thermometer;

  float targetTemperature;

  void incTemperature() { /* do something to increase room temperature */ }
  void decTemperature() { /* do something to decrease room temperature */ }

public:
  Thermostat(float target) : targetTemperature(target) {}

  bool ControlTemperature()
  {
    float temperature = thermometer.GetTemperature();

    if (temperature < targetTemperature)       incTemperature();     if (temperature > targetTemperature)
      decTemperature();

    return (temperature == targetTemperature);
};

The difference in the code here is subtle but in larger programs can cause a dramatic effect on the eventual code complexity and re-usability. In other languages, composition is preferred because C#, Java and PHP for example only support derivation from one base type per derived class, whereas C++ supports MI (multiple inheritance – deriving from more than one base class per derived class), so you do not need to be as concerned with your ability to organize a class hierarchy becoming limited.

When to derive and when to use composition

As a rule of thumb, when you want all public methods of a base class to be available to callers of a class with a modified implementation, use inheritance to create the modified class. When you only want some public methods to be available, use composition (or interface-only derivation in C++ where an interface is used) to make the modified class.

The examples above illustrate this. For squares and rectangles, we found out that while both share the GetArea() behaviour, we don’t want to expose methods allowing the width or height of a rectangle to be set when defining a square as all sides must have the same length. So, we extract GetArea() out into an interface and derive each shape type from this interface only, not from each other.

In the thermostat example, Thermometer exposes one public method – GetTemperature(). If we want users of the thermostat to be able to query the current room temperature, then we want the public method of Thermometer to be exposed and it makes sense to use derivation (example 1). If we want to keep this method hidden from thermostat users and only allow them to call ControlTemperature(), then it makes sense to use composition (example 2).

The square-rectangle issue can also be solved using only composition, although it is a bit of a quirky and forced example:

class Rectangle {
private:
  double width;
  double height;

public:
  Rectangle() {}
  Rectangle(double w, double h) : width(w), height(h) {}

  Rectangle &operator=(const Rectangle &r) { width = r.width; height = r.height; return *this; }

  double GetArea() { return width * height; }
};

class Square
{
private:
  Rectangle rect;

public:
  Square(double sideLength) { rect = Rectangle(sideLength, sideLength); }

  double GetArea() { return rect.GetArea(); }
};

Note that it is the constructor of Rectangle we want to hide here, but we still want to be able to use GetArea() so we have to provide a forwarding function to call GetArea() on the inner Rectangle member. We have to provide a default constructor in Rectangle for when an un-initialized Rectangle is declared as a member of Square, and an assignment operator overload for when rect is assigned to in Square‘s constructor.

For more information see the Liskov Substitution Principle.

Private inheritance

C++ provides a unique variant on derivation which is a form of syntactic sugar for composition, although with some important differences. By deriving a class as private instead of public, all public and protected members of the base class become private members of the derived class. Here is yet another version of the square-is-rectangle code:

class Rectangle {
private:
  double width;
  double height;

public:
  Rectangle(double w, double h) : width(w), height(h) {}

  double GetArea() { return width * height; }
};

class Square : private Rectangle
{
public:
  Square(double sideLength) : Rectangle(sideLength, sideLength);

  using Rectangle::GetArea;
};

All of the members of Rectangle become private members of Square, so if you had implemented setter methods for the width and height, they are now hidden from users of Square, which is what we want. Additionally, we can expose Rectangle‘s GetArea() method with the using keyword to save the code we duplicated in the solution which used composition above. Further, because we derived the class as private, users of the classes cannot cast them as pointers to each other anymore as you can with normal public derivation (good if you want homogenous storage, bad if you want hetereogenous storage, but the behaviour is essentially the same as with composition).

There are some downsides to private inheritance. The obvious main ones are that you can only have one instance of the base class in the derived class, so if you need multiple instances you need to use composition; also, using private derivation can lead to unnecessary MI in bigger class hierarchies. There are also issues with members being able to upcast pointers to the object to its base classes. For more thorough details on the pros and cons, see the C++ FAQ: Private and Protected Inheritance.

For more information on the using keyword, see The using declaration and class members (at ibm.com)

Summary

In brief (and with caveats):

  • Prefer composition over derivation, unless you want all public methods of the base class to be available to derived class users, in which case prefer derivation over composition
  • Prefer standard composition over composition via private inheritance
  • Use interfaces to relate otherwise-distinct types by their common features so they can be treated polymorphically

I hope you found this walkthrough useful. Please leave your feedback below!

Advertisements
Categories: C++ Tags: , , , ,
  1. March 5, 2013 at 17:19

    Thanks for the cite! Your proposed solution is a good one and my favorite as well. I wonder how the Liskov Substitution Principle might be different if Liskov had the OO options we have, today, or if there would even -be- a Liskov Substitution Principle.

    • March 6, 2013 at 00:34

      No problem! This was part of a bigger article about how I organized a more complex hierarchy for platform game objects (I wanted to give justifications for the design choices), but it got so long I decided to make it complete and put it in an article of its own. I chose the square-rectangle problem pretty arbitrarily, then Googled to see what other people had written – hotly debated stuff! Like you (if I understand your article correctly) I’m not particularly an LSP purist; the principle of derived type substitution is sound but I think the productivity and maintainability cost of violating LSP-style contracts is over-rated when you generally know what types you are working with anyway. The ‘correct’ answer to the problem all depends on the application and how the classes will be used, IMHO.

  2. Joseph
    June 5, 2015 at 00:47

    This is the most complete, intelligible and unambiguous explanation I’ve found. The contrasting examples are gems that seem to be missing from a good many articles that purport to be authoritative. Thank you for publishing this!

  1. January 4, 2014 at 00:37

Share your thoughts! Note: to post source code, enclose it in [code lang=...] [/code] tags. Valid values for 'lang' are cpp, csharp, xml, javascript, php etc. To post compiler errors or other text that is best read monospaced, use 'text' as the value for lang.

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

%d bloggers like this: