This page covers design conventions that you should adhere to. Some of them have proven useful for OO design in general, others are special to C++ applications.
Many tips are taken from the book [http://www.aw-bc.com/catalog/academic/product/0,1144,0321334876,00.html Effective C++] by Scott Meyers and from the [http://www.parashift.com/c++-faq-lite/index.html C++ FAQ Lite].
Index TableOfContents
Basics
References vs. pointers
Use a pointer whenever there is the possibility that it points to "nothing" (NULL, e.g. when signaling a function that nothing was passed as an argument) or whenever there is the possibility that you have to point to different things at different times. Use a reference when there will always be one and only one object that you want to refer to.
Use references when you can, and pointers when you have to.
See [http://www.parashift.com/c++-faq-lite/references.html#faq-8.6 this FAQ].
Never treat C-arrays as polymorphic objects
(BTW, use container classes rather than C-arrays, see [http://www.parashift.com/c++-faq-lite/containers.html#faq-34.1 this FAQ])
Never do this:
class Fruit { double weight; ... }; class Apple : public Fruit { ... more members ... }; double computeWeights(const Fruit array[], int nElements) { double w = 0.0; for(int i = 0; i < nElements; i++) w += array[i].weight; // BANG? return w; } Apple appleArray[10]; ... double w = computeWeights(appleArray, 10); // probably BANG above
array[i] == *(array + i), and the compiler constructed code for objects of Fruit size, so chances are high that with more members in Apple the above code refers to an illegal memory location. Polymorphism and pointer arithmetics do not mix.
Inheritance and object oriented design
Make sure (public) inheritance models "is a"
Whenever you derive a class D from a class B, every object of type D is a B in the sense that the derived class objects must be substitutable for the base class objects.
Do not do it as I (Joachim) have seen it in a real-world project: A person has properties, so CPerson inherits form CPersonProperties, which is a specialization of CProperties. This means, each CPerson is a CProperty, that is, each person is a property, which is non-sense at best, a catastrophic design flaw at worst.
Substitutability means means that objects of the derived class must behave in a manner consistent with the promises made in the base class' contract. Class D (and its methods) must require no more and promise no less than class B (and its overridden methods):
http://www.parashift.com/c++-faq-lite/proper-inheritance.html
In general, forget about "generalization" and "specialization". Think in terms of substitutability: In OO, a circle (which has one member for its radius) is not a specialization of an ellipse (which has two), though in mathematics it is one. The question is whether a circle can be put in wherever an ellipse can. (Most probably not, because it requires more: Even if a circle is considered to be an ellipse, it additionally requires that its two radii are the same.)
In the same sense, an ostrich (which cannot fly) is not a bird (if each bird has a method fly() that augments the bird's height over the ground):
http://www.parashift.com/c++-faq-lite/proper-inheritance.html#faq-21.6
Differentiate between inheriting an interface, inheriting an implementation, and inheriting both of them
When should you make a method purely virtual, when non-purely (impurely?) virtual, and when non-virtual?
The purpose of declaring a pure virtual function is to have derived classes inherit a function interface only. (You know too less for an implementation.)
The purpose of declaring a simple (non-pure) virtual function is to have derived classes inherit a function interface as well as a default implemenation. (You can and want to give a default implementation, but let derived classes decide whether to override it.)
The purpose of declaring a non-virtual function is to have derived classes inherit a function interface as well as a mandatory implementation. (You want all derived classes to have exactly the same behavior.)
class Car { public: virtual void driveToLocation(const Location& loc) = 0; // 1. Depends too much on the concrete car model (this makes the class abstract) virtual void hoot() { std::cout<< "Hoooooooot!" << std::endl;} // 2. Some models may make a noise of their own const string getNumberPlate() { return _numberPlate; } // 3. Must be exactly this for all models private: string _numberPlate; }
Do not make all member functions virtual a priori. Do not make all member functions non-virtual a priori. Differentiate!
Never redefine an inherited non-virtual function
Do not do this:
class B { void foo(); // non-virtual, keyword "virtual" missing }; class D : public class B { void foo(); // redefine B::foo() };
Rationale: Because D is (publicly) derived from B, every D object is a B object. If D really needs to implement foo() differently from B, and if every B object really has to use B's implementation, then D is not a B and so should not be (publicly) derived from B, a contradition!
Never redefine an inherited parameter value
enum Color { RED, GREEN }; class Shape { public: virtual void draw(Color color = RED) = 0; }; class Rectangle : public Shape { public: virtual void draw(Color color = GREEN); // BAD: parameter value is statically bound }; // in main.cpp: Shape* pr = new Rectangle(); pr->draw(); // calls Rectangle::draw( RED ) !!!
Parameter values are statically bound, but virtual functions are dynamically bound. This will shurely confuse you or the client of your code.
Avoid downcasts
Downcasts typically lead to code that finds code: "If the object is of type A then do X, if the object is of type B then do Y". This might become a maintanance nightmare. Use inheritance and virtual functions instead.
Inheritance vs. templates
Use a template to generate a collection of classes when the type of the objects does not affect the behavior of the class's functions: The behavior of stack<int>::pop() is quite the same as the behavior of stack<Car>::pop().
Use inheritance to generate a collection of classes when the type of the objects does affect the behavior of the class's functions: The behavior of Poodle::bark() is different from the behavior of Sheepdog::bark() (which are both implementations of the pure virtual Dog::bark()).
Model "has-a" or "is-implemented-in-terms-of" by layering (composition) whenever you can, by private inheritance when you must
Both techniques are for modeling "has-a" or "is-implemented-in-terms-of" relationships.
Layering is also known as composition, aggregation, containment, and embedding:
class Person { public: ... private: string name; // layered object Address address; // layered object ... }
Private inheritance is a syntactic variant of composition.
class Engine { public: Engine(int numCylinders); void start(); // Starts this Engine }; class Car : private Engine { // Car has-a Engine public: Car() : Engine(8) { } // Initializes this Car with 8 cylinders using Engine::start; // Start this Car by starting its Engine };
In contrast to public inheritance, compilers will not convert a derived class object into a base class object if the inheritance relationship is private. Members inherited from a private base class become private members of the derived class (even if they were protected or public in the base class.)
Private inheritance means "is-implemented-in-terms-of". It is purely a an implementation technique that you can use when you want to take advantage of some of the code that has already been written for the base class, not because there is any conceptual relationship between the base class and derived class.
You must use this technique (instead of layering) when protected members or virtual functions enter the picture. Suppose you want to write a class GenericStack that no one shall be able to instantiate, but whose code can be used by concrete classes such as IntStack with type-safety (that is, with an interface free of void* pointers):
class GenericStack { protected: GenericStack(); ~GenericStack(); void push(void* object); // not a pure virtual function! void* pop(); // there is code full of void* bool empty() const; // that can be reused private: // all stuff needed to create a linked list struct StackNode { ...} StackNode* top; ... } GenericStack s; // error! constructor is intentionally protected class IntStack: private GenericStack { public: void push(int *intPtr) {GenericStack::push(intPtr);} // reuse code of GenericStack int* pop() {return static_cast<int*>(GenericStack::pop());} // here, you know what you do! bool empty() const {return GenericStack::empty();} }
By deriving IntStack privately from GenericStack, every IntStack object "has-a" GenericStack object inside itself and can call the member functions of GenericStack (which, in turn, may access the member variables of the GenericStack object.)
This would not work with layering (composition) because if IntStack had a (private) member of class GenericStack to model "is-implemented-in-terms-of", the constructor of GenericStack had to be public, which would allow everyone to create GenericStack objects, pushing ints into them (via casts to void*), and popping RollingStones out of them (via casts from void*).
See also [http://www.parashift.com/c++-faq-lite/private-inheritance.html#faq-24.3 this FAQ].
You can now even use templates to generate type-safe stacks of any kind:
template class<T> class Stack: private GenericStack { public: void push(T *objectPtr) {GenericStack::push(objectPtr);} T* pop() {return static_cast<T*>(GenericStack::pop());} bool empty() const {return GenericStack::empty();} }
Note that the default inheritance relationship in C++ is private:
class B {}; class D : B {}; // without keyword "public", D is privately derived from B
Use multiple inheritance judiciously
Multiple inheritance has many dangers:
- Ambiguity: If C derives from A and B, and A and B have the same method m(), a call to m() via a pointer to a C object is ambiguous and therefore an error.
- The diamond shaped inheritance hierarchy ("dreaded diamond"):
A / \ / \ / \ B C \ / \ / \ / D class A {...}; class B: virtual public A {....}; class C: virtual public A {....}; class D: public B, public C {...};
If you need to create such a hierarchy, you should make A a virtual base class of B and C. Virtual inheritance means that a virtual base class will be represented by a single object of that class in every class somehow derived from it. (If B and C were non-virtually derived in the above example, a D object would contain two A objects.) This typically imposes an additional cost in both space and time on clients of A, because virtual base classes are often implemented as pointers to objects, rather than as objects themselves.
Also take care of the following initialization problem: For constructors of a virtual base class, the arguments are specified in the member initialization lists of the classes that are most derived from the base (as opposed to non-virtual bases, where a class of level n in the inheritance hierarchy passes constructor arguments to classes at level n-1.)
If you can avoid the use of virtual bases, that is, the creation of the dreaded diamond inheritance graph, things become much more manageable.
Tip: Avoid putting data members in virtual base classes. (Then there is no initialization problem). This is what Java calls an interface.
A clean example of multiple inheritance with a virtual base class is the following:
class Window; // domething to draw onto the screen class GraphicsObject { // could be a PNG image, a SVG image, ... public: virtual ~GraphicsObject(); virtual void draw(Window& w) const = 0; // object knows how to draw itself }; class GeometricObject { // a circle, a triangle, ... public: ~GeometricObject(); GeometricObject(int x, int y) : xpos(x), ypos(y) {}; private: int xpos, ypos; }; class DrawableCircle: virtual public GraphicsObject, public GeometricObject { public: DrawableCircle(int x, int y, int r) : GeometricObject(x, y), radius(r) {}; void draw(Window& w) { /* Bresenham's algorithm */ }; private: int radius; };
See also [http://www.parashift.com/c++-faq-lite/multiple-inheritance.html this section of the FAQ].
Make non-leaf classes abstract
(This is item 33 from [http://proquest.safaribooksonline.com/9780321545190 More Effective C++].)
The item starts with a discussion about how complicated it is to correctly implement operator= in a situation where you have a concrete base class B and you want to derive two concrete base classes C and D from B. The proposed solution is to find a common abstract base class A from which B', C, and D are derived, where B' is the "concrete part" of B and A is the "abstract part" of B.
Aside from this technical reasoning, the rationale goes like this: Replacing concrete base classes with abstract base classes forces the designer to explicitly recognize the existence of useful abstractions, that is, of useful concepts, even if the designer was not aware of the fact that useful concepts exist.
So when you want to (publicly) derive a concrete class C1 from a concrete class C2, better transform this into a three-class hierarchy by creating a new abstract base class A and let C1 and C2 inherit from it.
Construction, destruction, and assignment
Make destructors virtual in base classes
Good rule of thumb: Make the destructor virtual if and only if the class contains at least one virtual function.
http://www.parashift.com/c++-faq-lite/virtual-functions.html#faq-20.7
Rationale: When you try to delete a derived class object through a base class pointer and the base class has a non-virtual destructor, the results are undefined!
On the contrary, if a class does not contain any virtual functions, this is often an indication that it is not meant to be used as a base class. Then making the destructor virtual adds additional information to an object of this class (the vptr, "virtual table pointer"), which unnecessarily enlarges the object:
class Point { // for 2D points public: Point(short xCoord, short yCoord); ~Point(); private: short x, y; }
If a short is 16 bits, then an object of the above class does not fit any more into a 32-bit register.
Abide by the Law Of The Big Three
The Big Three are the destructor, the assignment operator and the copy constructor. In general, a class with any of the Big Three needs all of them.
A class with dynamically allocated memory should have the Big Three implemented.
Explicitly forbid the use of implicitly generated member functions that you do not want
template <class T> class Array { private: // explicitly forbid use of assignment operator Array& operator=(const Array& rhs); }
This technique prevents compilers from generating their own version and people from calling it.
Exceptions
Use exception specifications cautiously
An exception specification says what exceptions a function may throw:
void writeData(const FilePath& f) throw(IOException) { ... if( (fd = fopen(path, mode)) == NULL) throw IOException(); ... }
Facts:
- A function that has no exception specification is allowed to throw any exception.
A function that has an empty exception specification (writeData(const FilePath& f) throw()) is not allowed to throw any exception.
- The compiler does not check the exception specification of a function g() called from function f().
- If a function f() specifies the exceptions it may throw and it throws a different exception, the function std::unexpected() is called. This in turn calls std::terminate(), which in turn calls abort().
- If g() throws an exception that is not listed in the specification of f() and f() does not catch this exception, the program will terminate.
Bottom line: Be cautious with exception specifications when your function calls functions whose possibly thrown exceptions you do not fully know. This is especially the case with functions from 3rd party libraries.
Techniques
Ensure that non-local static objects are initialized before the are used
Suppose in one translation unit you define a printer:
class Printer {...}; Printer thePrinter; // the one and only printer in your system
Suppose in a different translation unit you define a printer queue that of course uses your printer:
class PrinterQueue { public: PrinterQueue(); ... }; PrinterQueue::PrinterQueue() { // get some information from the printer, for example, the file format it accepts } PrinterQueue thePrinterQueue; // the one and only printer queue in your system
You must ensure that the thePrinter object is initialized before the thePrinterQueue object.
But: You have no control over the order in which non-local static objects in different translation units are initialized. In the most general form of the problem (with such objects generated via implicit template instantiations), the proper order in which to initialize non-local static objects cannot be determined by the compiler because it is equivalent to the Halting problem!
You can workaround the problem by using a variant of the [http://en.wikipedia.org/wiki/Singleton_pattern Singleton pattern]:
class Printer {...}; // as before Printer& thePrinter() { static Printer tp; return tp; } class PrinterQueue{...}; // as before PrinterQueue& thePrinterQueue { static PrinterQueue tpq; return tpq; }
Now you use a function instead of a global variable to access the printer (and the printer queue). The static variable inside this function is initialized the first time the function is called. If it is never called (accessed), the object is never created, which is also an improvement.
Miscellaneous
Use the new C++ casting operators
Do not use the old C-style casts. The new casting operators stand out in the code, respect constness and access control, and some of them give feedback in case the cast cannot be performed.
Use static_cast<T>() to tell the compiler "trust me":
int x = 4, y = 5; ... double d = static_cast<double>(x) / y;
Use dynamic_cast<T*>() to perform safe casts down (to children) or across (to siblings) an inheritance hierarchy. If the cast is not valid, NULL is returned in case a pointer is cast, and an exception of type bad_cast is thrown in case a reference is cast, In general, you should avoid downcasts, of course!
void foo(Shape* p) { Circle* cp = dynamic_cast<Circle*>(p); if(cp != NULL) // the object is a circle else // the object is not a circle }
Use const_cast<T*>() to cast away the constness of an expression.
There is a forth cast, reinterpret_cast, whose result is implementation-defined and rarely portable. It is used to cast between function pointer types.