Class Design and Inheritance
Team LiB
Previous Section Next Section

Class Design and Inheritance

32. Be clear what kind of class you're writing.

Know thyself: There are different kinds of classes. Know which kind you are writing.

33. Prefer minimal classes to monolithic classes.

Divide and conquer: Small classes are easier to write, get right, test, and use. They are also more likely to be usable in a variety of situations. Prefer such small classes that embody simple concepts instead of kitchen-sink classes that try to implement many and/or complex concepts (see Items 5 and 6).

34. Prefer composition to inheritance.

Avoid inheritance taxes: Inheritance is the second-tightest coupling relationship in C++, second only to friendship. Tight coupling is undesirable and should be avoided where possible. Therefore, prefer composition to inheritance unless you know that the latter truly benefits your design.

35. Avoid inheriting from classes that were not designed to be base classes.

Some people don't want to have kids: Classes meant to be used standalone obey a different blueprint than base classes (see Item 32). Using a standalone class as a base is a serious design error and should be avoided. To add behavior, prefer to add nonmember functions instead of member functions (see Item 44). To add state, prefer composition instead of inheritance (see Item 34). Avoid inheriting from concrete base classes.

36. Prefer providing abstract interfaces.

Love abstract art: Abstract interfaces help you focus on getting an abstraction right without muddling it with implementation or state management details. Prefer to design hierarchies that implement abstract interfaces that model abstract concepts.

37. Public inheritance is substitutability. Inherit, not to reuse, but to be reused.

Know what: Public inheritance allows a pointer or reference to the base class to actually refer to an object of some derived class, without destroying code correctness and without needing to change existing code. Know why: Don't inherit publicly to reuse code (that exists in the base class); inherit publicly in order to be reused (by existing code that already uses base objects polymorphically).

38. Practice safe overriding.

Override responsibly: When overriding a virtual function, preserve substitutability; in particular, observe the function's pre- and post-conditions in the base class. Don't change default arguments of virtual functions. Prefer explicitly redeclaring overrides as virtual. Beware of hiding overloads in the base class.

39. Consider making virtual functions nonpublic, and public functions nonvirtual.

In base classes with a high cost of change (particularly ones in libraries and frameworks): Prefer to make public functions nonvirtual. Prefer to make virtual functions private, or protected if derived classes need to be able to call the base versions. (Note that this advice does not apply to destructors; see Item 50.)

40. Avoid providing implicit conversions.

Not all change is progress: Implicit conversions can often do more damage than good. Think twice before providing implicit conversions to and from the types you define, and prefer to rely on explicit conversions (explicit constructors and named conversion functions).

41. Make data members private, except in behaviorless aggregates (C-style structs).

They're none of your caller's business: Keep data members private. Only in the case of simple C-style struct types that aggregate a bunch of values but don't pretend to encapsulate or provide behavior, make all data members public. Avoid mixes of public and nonpublic data, which almost always signal a muddled design.

42. Don't give away your internals.

Don't volunteer too much: Avoid returning handles to internal data managed by your class, so clients won't uncontrollably modify state that your object thinks it owns.

43. Pimpl judiciously.

Overcome the language's separation anxiety: C++ makes private members inaccessible, but not invisible. Where the benefits warrant it, consider making private members truly invisible using the Pimpl idiom to implement compiler firewalls and increase information hiding. (SeeItems 11 and 41.)

44. Prefer writing nonmember nonfriend functions.

Avoid membership fees: Where possible, prefer making functions nonmember nonfriends.

45. Always provide new and delete together.

They're a package deal: Every class-specific overload void* operator new(parms) must be accompanied by a corresponding overload void operator delete(void*, parms), where parms is a list of extra parameter types (of which the first is always std::size_t). The same goes for the array forms new[] and delete[].

46. If you provide any class-specific new, provide all of the standard forms (plain, in-place, and nothrow).

Don't hide good news: If a class defines any overload of operator new, it should provide overloads of all three of plain, in-place, and non-throwing operator new. If you don't, they'll be hidden and unavailable to users of your class.

    Team LiB
    Previous Section Next Section