Optimizing C++/Writing efficient code/Constructions and destructions

From Wikibooks, open books for an open world
Jump to: navigation, search

Construction and destruction of an object requires time, especially if the object owns other objects.

This section will provide guidelines for decreasing the number of object constructions and corresponding destructions.

Variable scope[edit]

Declare variables as late as possible.

To do so, the programmer must declare all variables in the most local scope. By doing so, the variable is neither constructed nor destructed if that scope is never reached. Postponing declaration as far as possible within a scope means that should there be an early exit before the declaration (using a return or break or continue statement) the object associated to the variable is neither constructed nor destructed.

It is often the case that at the beginning of a routine no appropriate value is available with which to initialize a variable. The variable is therefore initialized with a default value and a later assignment sets the correct value when it becomes available. If, instead, the variable is defined only when an appropriate value is available, the object is initialized with this value and no subsequent assignment is necessary. This is advised by the guideline "Initializations" in this section.

Initializations[edit]

Use initializations instead of assignments. In particular, in constructors, use initialization lists.

For example, instead of writing:

string s;
...
s = "abc"

write:

string s("abc");

Even if a class instance (s in the first example above) is not explicitly initialized, it is nevertheless automatically initialized by the default constructor.

To call the default constructor followed by an assignment with a value may be less efficient than to call only a constructor with the same value.

Increment/decrement operators[edit]

Use prefix increment (++) or decrement (--) operators instead of the corresponding postfix operators if the expression value is not used.

If the incremented object is a primitive type, there is no difference between prefix and postfix operators. However, if it is a composite object, the postfix operator causes the creation of a temporary object, while the prefix operator does not.

Because every object that is a primitive type may become a composite object in the future, it is better to use the prefix operator whenever possible, especially when writing generic (templatized) code that operates on iterators.

Use the postfix operator only when the variable is in a larger expression and must be incremented only after the expression is evaluated.

Assignment composite operators[edit]

Use the assignment composite operators (like in a += b) instead of simple operators combined with assignment operators (like in a = a + b).

For example, instead of the following code:

string s1("abc");
string s2 = s1 + " " + s1;

write the following code:

string s1("abc");
string s2 = s1;
s2 += " ";
s2 += s1;

Typically, a simple operator creates a temporary object. In the example, the operator + creates temporary strings whose creation and destruction require much time.

On the contrary, the equivalent code using the += operator does not create temporary objects.

Function argument passing[edit]

When you pass an object x of type T as argument to a function, use the following criterion:

  • If x is an input-only argument,
    • if x may be null,
      • pass it by pointer to constant (const T* x),
    • otherwise, if T is a fundamental type or an iterator or a function-object,
      • pass it by value (T x) or by constant value (const T x),
    • otherwise,
      • pass it by reference to constant (const T& x),
  • otherwise, i.e. if x is an output-only or input/output argument,
    • if x may be null,
      • pass it by pointer to non-constant (T* x),
    • otherwise,
      • pass it by reference to non-constant (T& x).

Pass by reference is more efficient than pass by pointer as it facilitates variable elimination by the compiler and because the callee need not check whether the reference is valid or null. However, where the argument can be missing, it is more efficient to pass a NULL pointer, than to pass a reference to a possibly dummy object and a boolean indicating whether the reference is valid.

For objects that may be contained in one or two registers, pass by value is more efficient than pass by reference (or equally efficient). This applies to tiny objects, such as the fundamental types, iterators and function-objects. For larger objects, pass by reference is more efficient than pass by value, as with the latter, the object must be copied onto the stack.

A composite object that is currently fast to copy might be passed efficiently by value. However, unless the object is an iterator or a function-object (which are assumed always to copy efficiently), this technique is risky. Future changes to the object might increase its size and make it more expensive to copy. For example, if an object of class Point contains only two floats, it could be efficiently passed by value; but if in the future a third float is added, or if the two floats become two doubles, it could become more efficient pass by reference.

explicit declaration[edit]

Declare as explicit all constructors that receive only one argument, except for the copy constructors of concrete classes.

Non-explicit constructors may be called automatically by the compiler when it performs an automatic (implicit) type conversion. The execution of such constructors may take much time.

If such conversion is made compulsorily explicit, and if a new class name is not specified in the code, the compiler could choose another overloaded function, avoiding to call the costly constructor, or it could generate an error, so forcing the programmer to choose another way to avoid the constructor call.

For copy constructors of concrete classes a distinction must be made to allow their pass by value. For abstract classes, even copy constructors may be declared explicit, as, by definition, abstract classes cannot be instantiated and so objects of such type should never be passed by value.

Conversion operators[edit]

Declare conversion operators only to keep compatibility with an obsolete library (in C++11, declare them explicit).

Conversion operators allow implicit conversions and so incur in the same problem as implicit constructors described in the guideline "explicit declaration" in this section.

If such conversions are needed, provide instead an equivalent member function, as it may only be called explicitly.

The only acceptable remaining usage for conversion operators is when a new library must coexist with an older similar library. In such a case, it may be convenient to have operators that automatically convert objects from the old library into the corresponding types of the new library and vice versa.

The Pimpl idiom[edit]

Use the Pimpl idiom only when you want to make the rest of the program independent from the implementation of a class.

The Pimpl idiom (meaning Pointer to implementation) consists of storing in an object only a pointer to a data structure containing all the useful information about the object.

The main advantage of the idiom is that it speeds up incremental compilation of code by making it less likely that a small change in the source code causes the need to recompile a large number of code lines.

This idiom also makes some operations more efficient, such as a swap of two objects. In general, however, it slows down every access to the object data because of the added level of indirection and causes an additional memory allocation each time such an object is created or copied. It should not, therefore, be used for classes whose public member functions are called frequently.

Iterators and function objects[edit]

Ensure that custom iterators and function objects are tiny and do not allocate dynamic memory.

STL algorithms pass such objects by value. Therefore, if their copy is not extremely efficient, STL algorithms are slowed down.

If an iterator or function object for some reason needs an elaborate internal state, allocate it dynamically and use a shared pointer. For example, say you want to implement an STL-compliant 32-bit Random Number Generator on top of the Linux/OpenBSD /dev/urandom device:

#include <boost/shared_ptr.hpp>
#include <fstream>
 
class urandom32 {
        boost::shared_ptr<std::ifstream> device;
 
  public:
        urandom32() : device(new std::ifstream("/dev/urandom")) { }
 
        uint32_t operator()()
        {
                uint32_t r;
                device->read(reinterpret_cast<char *>(&r), sizeof(uint32_t));
                return r;
        }
};

In this case, the use of a pointer was actually necessary because the ifstream class is non-copyable: its copy constructor is declared private. This example uses the boost::shared_ptr smart pointer; for more speed, intrusive reference counting could be used.