?

Log in

No account? Create an account

Previous Entry | Next Entry

Small, Orthogonal, and Loosely Coupled

Here are some concepts (this is going to be a live entry) that have bubbled to the top from reading lots of Meyers, Sutter and Alexandrescu...

Avoid Inheritance.

Inheritance is a choice that tightly couples the classes together. When you can, prefer containment to inheritance, because the looser the coupling, the better. When you do inherit, inherit not to reuse (code in the base class), but to be reused (by existing code that already uses the base objects polymorphically). See item 37 of the C++ Coding Standards.


Avoid Member Functions.

Nonmember nonfriend functions improve encapsulation by minimizing dependencies and reducing coupling. Herb Sutter is fond of pointing out that std::string has 71 member functions (out of 103) that could have been implemented as non-member non-friend functions without loss of efficiency. See item 44 of the C++ Coding Standards.


Know What Kind of Class You're Writing

Your classes should fall squarely into one of the following classes. See item 32 of the C++ Coding Standards. Your class should be one of the following:


  • Value Class (Concrete class with value semantics. e.g., std::pair, std::vector)

  • Base Class (Interface class with virtual functions for providing implementation. See destructor comment below.)

  • Traits Class (Templates with typedefs and static functions.)

  • Policy Class (For pluggable behavior. E.g., Observer's Monitor policy.)

  • Exception Class (Throw by value, catch by reference, may clone, probably inherits from std::exception)



If You Have to Write Destructors...

(And you probably do, if you're implementing RAII...)

...Make base class destructors public and virtual, or protected and nonvirtual.

If your base class can be destructed through a pointer, make the destructor virtual, otherwise make it nonpublic (so calling code can't call it) and nonvirtual (because it needn't be virtual), See item 50 of the C++ Coding Standards.


...You probably have to write the copy constructor and copy assignment operator.

If you don't, the compiler will write them for you, and make them public and wrong. You should either make them public and work correctly for your classes with value semantics, or private to prevent the compiler from allowing value-like behavior. See item 52 of the C++ Coding Standards.



Consider Implementing a No-Fail Swap (or std::swap specialization)

(This is for classes with value semantics.) Swap is a handy method instrumental in implementing assignment easily and providing a guaranteed commit function that enables strongly error-safe calling code. See item 56 of the C++ Coding Standards.


Consider the Non-Virtual Interface Pattern

Prefer to make pubic functions nonvirtual. Prefer to make virtual functions private, or protected if derived class need them. A Public virtual function serves two purposes (a bad thing), it specifies interface and it specifies implementation detail. See item 39 of the C++ Coding Standards. And consider this implementation of Clone() to avoid slicing:

Class B { //...
public:
  B* Clone() const {
    B* p = DoClone();
    assert(typeid(*p) == typeid(*this) && "DoClone incorrectly overridden");
    return p;
  }
protected:
  B (const B&);
  virtual B* DoClone() const = 0;
};