Development guide

Classes

Access rights

Do not specify public or protected member data in a class.

The use of public variables is discouraged for the following reasons:

  1. A public variable represents a violation of one of the basic principles of object-oriented programming, namely, encapsulation of data. For example, if there is a class of the type BankAccount, in which accountBalance is a public variable, the value of this variable may be changed by any user of the class. However, if the variable has been declared private, its value may be changed only by the member functions of the class.
  2. An arbitrary function in a program can change public data, leading to errors that are difficult to locate.
  3. If public data is avoided, its internal representation may be changed without users of the class having to modify their code. A principle of class design is to maintain the stability of the public interface of the class. The implementation of a class should not be a concern for its users.

The use of protected variables in a class is not recommended, since they become visible to its derived classes. The names of types or variables in a base class may then not be changed since the derived classes may depend on them. If a derived class, for some reason, must access data in its base class, one solution may be to make a special protected interface in the base class, containing functions that return private data. This solution is not likely to imply any degradation of performance if the functions are defined inline.

The use of structs is also discouraged since these only contain public data. In interfaces with other languages (such as C), it may, however, be necessary to use structs.

Inline functions

Access functions (accessors) that simply return the value of a member variable are inline.
Forwarding functions are inline.
Constructors and destructors should not be inline.

The normal reason for declaring a function inline is to improve its performance. Small functions, such as access functions, which only return the value of a member of the class and so-called forwarding functions that invoke another function should normally be inline. Correct usage of inline functions may also lead to reduced code size.

Warning: functions invoking other inline functions often become too complex for the compiler to be able to make them inline despite their apparent smallness. This problem is especially common with constructors and destructors. A constructor always invokes the constructors of its base classes and member data before executing its own code. Inline constructors and destructors are best avoided.

Friends

Friends of a class should be used to provide additional functions that are best kept outside of the class.

Operations on an object are sometimes provided by a collection of classes and functions. A friend is a nonmember of a class that has access to the nonpublic members of the class. Friends offer an orderly way of getting around data encapsulation for a class. A friend class can be advantageously used to provide functions that require data that is not normally needed by the class.

Suppose there is a list class that needs a pointer to an internal list element in order to iterate through the class. This pointer is not needed for other operations on the list. There may then be reason, such as obtaining smaller list objects, for a list object not to store a pointer to the current list element and instead to create an iterator, containing such a pointer, when it is needed. One problem with this solution is that the iterator class normally does not have access to the data structures that are used to represent the list (since we also recommend private member data). By declaring the iterator class as a friend, this problem is avoided without violating data encapsulation.

Friends are good if used properly. However, the use of many friends can indicate that the modularity of the system is poor.

Const member functions

A member function that does not affect the state of an object (its instance variables) is declared const.
If the behavior of an object is dependent on data outside the object, this data is not modified by const member functions.

Member functions declared as const must not modify member data and are the only functions that may be invoked on a const object. (Such an object is clearly unusable without const methods.) A const declaration is an excellent insurance that objects will not be modified (mutated) when they should not be. A great advantage of C++ is the ability to overload functions with respect to their const-ness. Two member functions may have the same name where one is const and the other one is not. Non-const member functions are sometimes invoked as so-called "lvalues" (as a location value at which a value may be stored). A const member function may never be invoked as "lvalue".

The behavior of an object can be affected by data outside the object. Such data must not be modified by a const member function. In rare cases, it is desirable to modify member variables in a const object. Such member variables must be declared as mutable.

Constructors and destructors

Every class that has pointers as instance variables must declare a copy constructor (and an assignment operator). The copy constructor may be private, in which case no implementation is provided.
All classes that can be used as base classes must define a virtual destructor.
If a class has at least one virtual function, it must also have a virtual destructor.
Every class must have a default (no-argument) constructor and a destructor. Unless the default constructor is private, an implementation (even if empty) must be provided for the default constructor. For the destructor, an implementation must always be provided.
Define and initialize member variables in the same order. Prefer initialization to assignment in constructors.
Do not call a virtual function from a constructor or destructor.
Avoid the use of global objects in constructors and destructors.
Use explicit for single argument non-copy constructors. This helps to avoid ambiguities caused by implicit type conversions.

A copy constructor is recommended to avoid surprises when an object is initialized using an object of the same type. If an object manages the allocation and deallocation of an object on the heap (the managing object has a pointer to the object to be created by the class' constructor), only the value of the pointer will be copied. This can lead to two invocations of the destructor for the same object (on the heap), probably resulting in a run- time error.

The corresponding problem exists for the assignment operator ("="). If a class, having virtual functions but without virtual destructors, is used as a base class, there may be a nasty surprise if pointers to the class are used. If such a pointer is assigned to an instance of a derived class and if delete is then used on this pointer, only the base class' destructor will be invoked. If the program depends on the derived class' destructor being invoked, the program will fail.

In connection with the initialization of statically allocated objects, it is not guaranteed that other static objects will be initialized (for example, global objects). This is because the order of initialization of static objects that are defined in different compilation units is not defined in the language definition.

// Initialization of member variables in a constructor:
class Property
{
public:
    // Constructor
    Property(const std::string &name, const std::string &value);
    ...

private:
    std::string propertyName;
    std::string propertyValue;
};

// Good:
Property::Property(const std::string &name, const std::string &value)
    :
        propertyName(name),
        propertyValue(value)
{
}

// Bad:
Property::Property(const std::string &name, const std::string &value)
{
    propertyName = name;
    propertyValue = value;
}

Assignment operator

Every class that has pointers as instance variables must declare an assignment operator along with a copy constructor. The assignment operator may be private, in which case no implementation is provided.
An assignment operator performing a destructive action must be protected from performing this action on the object upon which it is operating.
An assignment operator shall return a non-const reference to the assigning object.
Whenever possible, the implementation of an assignment operator shall use the swap() operation to provide for strong exception safety.

An assignment is not inherited like other operators. If an assignment operator is not explicitly defined, then one is automatically defined instead. Such an assignment operator does not perform bit-wise copying of member data; instead, the assignment operator (if defined) for each specific type of member data is invoked. Bit-wise copying is only performed for member data having primitive types.

One consequence of this is that bit-wise copying is performed for member data having pointer types. If an object manages the allocation of the instance of an object pointed to by a pointer member, this will probably lead to problems: either by invoking the destructor for the managed object more than once or by attempting to use the deallocated object.

Operator overloading

If an assignment operator is overloaded, the programmer must make certain that the base class' and the members' assignment operators are run.

A common error is assigning an object to itself. Normally, destructors for instances allocated on the heap are invoked before assignment takes place. If an object is assigned to itself, the values of the instance variables will be lost before they are assigned. This may well lead to strange run-time errors.

// Implementing the assignment operator using swap():
Foo &Foo::operator= (const Foo &foo)
{
    Foo tmp(foo);
    swap(tmp);
    return *this;
}
Use operator overloading sparingly and in a uniform manner.
When two operators are opposites (such as == and !=), it is appropriate to define both.
Do not overload operator "", operator || or operator , (comma).
Do not overload type conversion (cast) operators.

Operator overloading has both advantages and disadvantages. One advantage is that code using a class with overloaded operators can be written more compactly (more readably). Another advantage is that the semantics can be both simple and natural. One disadvantage in overloading operators is that it is easy to misunderstand the meaning of an overloaded operator (if the programmer has not used natural semantics). The extreme case, where the plus-operator is re-defined to mean minus and the minus-operator is re-defined to mean plus, probably will not occur very often, but more subtle cases are conceivable.

Designing a class library is like designing a language! If you use operator overloading, use it in a uniform manner; do not use it if it can easily give rise to misunderstanding.

Member function return types

A public member function must never return a non-const reference or pointer to data outside an object, unless the object shares the data with other objects.

By allowing a user direct access to the private member data of an object, this data may be changed in ways not intended by the class designer. This may lead to reduced confidence in the designer's code: a situation to be avoided.

Inheritance

Avoid inheritance for parts-of relations. Prefer composition to inheritance.
Give derived classes access to class type member data by declaring protected access functions.

A common mistake is to use multiple inheritance for parts-of relations (when an object consists of several other objects, these are inherited instead of using instance variables. This can result in strange class hierarchies and less flexible code. In C++ there may be an arbitrary number of instances of a given type; if inheritance is used, direct inheritance from a class may only be used once.

A derived class often requires access to base class member data in order to create useful member functions. The advantage of using protected member functions is that the names of base class member data are not visible in the derived classes and thus may be changed. Such access functions should only return the values of member data (read-only access).

The guiding assumption is that those who use inheritance know enough about the base class to be able to use the private member data correctly, while not referring to this data by name. This reduces the coupling between base classes and derived classes.