Guidelines for Object Oriented Programming in C++ A language is object oriented if it has the following 3 features: 1. Data Abstraction (or encapsulation) - allows you to create new types (classes) with a well defined interface and hidden implementation. 2. Inheritance - allows you to group similar classes, reusing the class definitions. 3. Polymorphism - allows you to create a hierarchy of classes with a common interface. Object-oriented design is intended to reduce the size and maintenance costs of very large programs. 1. Data abstraction A class should have a public interface containing only methods (member functions) and a private implementation containing all data members. For instance: class Point { private: int x, y; // Data members public: Point(int a, int b=0): x(a), y(b) {} // Constructor void draw() const; // Method }; By convention, class names are uppercase and are typically nouns. Functions are verbs. Methods (like draw()) have access to private data (x, y). If you don't need this access, use a regular (global) function instead. Methods which do not modify data members (x, y) should be const. Methods which include code in the class definition are inlined. Functions longer than a few lines should not be inlined, i.e. void Point::draw() const { // ... } If the method has default arguments, they should be specified only once. It is usually safe for a method to return a member by reference or const reference. Methods that return by reference should not be const. The constructor should initialize each data member. The order of initialization is undefined (not x before y). If an initialization is omitted, then the compiler supplies a default initialization, i.e. Point(int a, int b=0): x(), y() A constructor with 1 parameter defines an implicit conversion unless the constructor is declared explicit. Point p = 3; // Point p(3, 0); p = 4; // p = Point(4) If you don't write any constructors, the compiler supplies a default: Point(): x(), y() {} The compiler supplies a default destructor, copy constructor, and assignment operator. These perform the corresponding operations on each data member: ~Point() {} Point(const Point& p): x(p.x), y(p.y) {} Point& operator=(const Point& p) { x = p.x; y = p.y; return *this; } Rule of 3: if you need to write any of the above, you need to write all of them. *this means self, e.g. this->x means x. Methods have access to private members of other objects of the same class (such as p.x). A function or a class can be declared a friend. A friend has access to private members. Don't use a friend when a method will do. class Point { friend void f(); friend class C; A static data member is shared by all objects of a class. It is initialized in a declaration outside the class. class Counter { private: static int count; // Total number of Counter objects public: Counter() {++count;} ~Counter() {--count;} // Rule of 3 applies Counter(const Counter& c) {++count;} Counter& operator=(const Counter& c) {return *this;} static int getCount() {return count;} }; int Counter::count = 0; // Initialize here Static methods do not require an object to call them. They do not have a *this, and can only refer to static members or members of named objects (not self). Static methods are never const. cout << Counter::getCount(); // May be 0 A struct is the same as a class, except the default protection is public instead of private. An operator may be overloaded either as a global function, with one parameter for each operand, or as a method of the left operand, e.g. a+b means either operator+(a, b) a.operator+(b) 2. Inheritance A derived class inherits all members of a base class except constructors, destructors, and the assignment operator. The base class should almost always be public. Derive from general to specific: class Derived: public Base { ... }; class Duck: public Bird { ... }; class Square: public Rectangle { ... }; A derived object may be converted to base. There is no conversion the other way unless you write one (a constructor). Base b = Derived(); Base *bp = new Derived(); Base& br = Derived(); Constructors should initialize the base class, not the inherited members: class Point3D: public Point { private: int z; // inherit x, y public: Point3D(int a, int b, int c): Point(a, b), z(c) {} }; The default methods treat the base as a data member: class Point3D: public Point { public: ~Point3D() {} // implicit ~Point(), z.~int() Point3D(const Point3D& p): Point(p), z(p.z) {} Point3D& operator=(const Point3D& p) { z = p.z; Point::operator=(p); // Explicit call to base method return *this; } }; Protected members may be accessed by derived classes, but are otherwise private. A derived class can have more than one base class (multiple inheritance). 3. Polymorphism Polymorphic class hierarchies consist of an interface (abstract base class) and a set of derived implementations. Typically the base has no data members or code, and the derived classes do not add any new public members (except constructors). class Shape { // Interface protected: Shape() {}; public: virtual void draw() const = 0; virtual ~Shape() {} }; class Polygon: public Shape { // An implementation public: void draw() const { /* code here */ } }; A method overriding a base method must have the same parameters, return type, and const-ness. An overridden method must be virtual (in the base class only) if the correct version is to be called through a base pointer or reference (run time decision). Shape *p = new Polygon(); p->draw(); // Polygon::draw() You should not create objects of an abstract class or interface. The abstractness of the base class can be enforced either by using protected (not private) constructors, or pure (= 0) virtual methods. (A private constructor would prohibit instantiation of derived objects as well). A class with overridden methods should have a public virtual destructor, even if it does nothing. The rule of 3 applies only if the destructor code is non-empty. delete p; // Which destructor? Design Guidelines Write an analysis. What does your program do? What does the user see and do? Decide what data you need to keep track of. Decide on a representation, using existing classes first (int, string, vector, map, etc.) For complex data structures (hash tables, high precision numbers, etc.) decide on a representation and a set of operations. These become the private and public parts of your classes. If you have a lot of classes, look for IS-A relationships between them and use inheritance (Derived IS-A Base). If your classes are related with no obvious base class (Square, Triangle), then create an abstract base class (Shape). If you need homogeneous containers (all objects of the same type), use templates. If you need heterogeneous containers (a mix of types) from a common hierarchy, use polymorphism instead. Algorithms on templated types are themselves templates. Algorithms on polymorphic data structures are coded as if for the base class, e.g. ostream& operator << (ostream& out, const Shape& s) { ... } works on an ofstream (derived from ostream) and Polygon (derived from Shape).