Discussion
Team LiB
Previous Section Next Section

Discussion

Inheritance is often overused, even by experienced developers. A sound rule of software engineering is to minimize coupling: If a relationship can be expressed in more than one way, use the weakest relationship that's practical.

Given that inheritance is nearly the strongest relationship we can express in C++, second only to friendship, it's only really appropriate when there is no equivalent weaker alternative. If you can express a class relationship using composition alone, you should prefer that.

In this context, "composition" means simply embedding a member variable of a type within another type. This way, you can hold and use the object in ways that allow you control over the strength of the coupling.

Composition has important advantages over inheritance:

  • Greater flexibility without affecting calling code: A private data member is under your control. You can switch from holding it by value to holding by (smart) pointer or Pimpl (see Item 43) without breaking client code; you would only need to change the implementations of the class's own member functions that use it. If you decide you need different functionality, you can easily change the type of the member or the manner of holding it while keeping the class's public interface consistent. In contrast, if you begin with a public inheritance relationship, it is likely that clients have already come to depend on the inheritance; you have therefore committed your class to it and cannot easily change your base class decision later on. (See Item 37.)

  • Greater compile-time insulation, shorter compile times: Holding an object by pointer (preferably a smart pointer), rather than as a direct member or base class, can also allow you to reduce header dependencies because declaring a pointer to an object doesn't require that object's full class definition. By contrast, inheritance always requires the full definition of the base class to be visible. A common technique is to aggregate all private members behind a single opaque pointer, called a Pimpl (see Item 43).

  • Less weirdness: Inheriting from a type can cause name lookup to pull in functions and function templates defined in the same namespace as that type. This is very subtle and hard to debug. (See also Item 58)

  • Wider applicability: Some classes were not designed to be bases in the first place (and see Item 35). Most classes, however, can fulfill the role of a member.

  • Great robustness and safety: The tighter coupling of inheritance makes it more difficult to write error-safe code. (See [Sutter02] §23.)

  • Less complexity and fragility: Inheritance exposes you to additional complications, such as name hiding and other complications that can arise in the presence of later changes to the base class.

Of course, these are not arguments against inheritance per se. Inheritance affords a great deal of power, including substitutability and/or the ability to override virtual functions (see Items 36 through 39, and Exceptions below). But don't pay for what you don't need; unless you need inheritance's power, don't endure its drawbacks.

    Team LiB
    Previous Section Next Section