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 to decrease the number of object constructions and corresponding destructions.

Contents

[edit] Variable scope

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 not constructed or destructed if that scope is never reached. To postpone the declaration as far as possible inside a scope causes that if before such declaration there is an early exit, using a return or break or continue statement, the object associated to the variable is not constructed nor destructed.

In addition, often at the beginning of a routine you haven't yet an appropriate value to initialize the object associated to the variable, and therefore you must initialize it with a default value, and then assign an appropriate value to it. Instead, if you declare such variable when you have available an appropriate value, you can initialize the object using such value, without needing a successive assignment, as advised by the guideline "Initializations" in this section.

[edit] Initializations

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 an instance of a class is not explicitly initialized, it is anyway 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.

[edit] Increment/decrement operators

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.

[edit] Assignment composite operators

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 do not create temporary objects.

[edit] Function argument passing

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

  • If x is a 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 as the callee hasn't to check if the reference is valid or null; though, the pointer has the advantage of being able to represent a null value, and it is more efficient to pass just a 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 or equally efficient than pass by reference, as such objects may be contained in registers and don't have indirection levels; therefore, this is the fastest way to pass surely tiny objects, as the fundamental types, the iterators and the function-objects. For objects that are larger than a couple of registers, pass by reference is more efficient than pass by value, as pass by value causes the copy into the stack of such objects.

A composite object that is fast to copy could be efficiently passed by value, but, except it is an iterator or a function-object, for which it is assumed the efficiency of the copy, this technique is risky, as in the future the object could become slower 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.

[edit] explicit declaration

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

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

If such conversion is made compulsorily explicit, and it new class name is not specified in the code, the compiler could choose another overloaded function, avoiding to call the costly constructor, or 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 definitions, abstract classes cannot be instantiated, and so objects of such type should never be passed by value.

[edit] Conversion operators

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

Conversion operators allow implicit conversions, and so incur in the same problem of 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 be called only explicitly.

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

[edit] The Pimpl idiom

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 in storing in the object only a pointer to a data structure containing all the useful information about such object.

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

Such idiom allows also to speed up some operations, like the swap of two objects, but in general it slows down every access to the object data because of the added indirection level, and causes an added memory allocation for every creation or copy of such object. Therefore it shouldn't be used for classes whose public member functions are called frequently.

[edit] Iterators and function objects

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 of function object for some reason needs 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.

Personal tools
Namespaces
Variants
Actions
Navigation
Community
Toolbox
In other languages
Sister projects
Print/export