Optimizing C++/Code optimization/Constructions and destructions

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

Often it happens that, while processing an expression, a temporary object is created, which is destroyed at the end of that expression. If such object is of a fundamental type, almost always the compiler succeeds in avoiding its creation, and anyway the creation and destruction of an object of a fundamental type are quite fast. Instead, if the object is of a composite type, its creation and destruction have an unlimited cost, as they cause the call of a constructor and the call of the destructor, that may take any time.

In this section some techniques are presented to avoid that composite temporary objects are created, and therefore that their constructors and destructors are called.

Functions return value[edit | edit source]

For non-inlined functions, try to declare a return type for which an object copy moves no more than 8 bytes. If unfeasible, at least construct the result object in the return statement.

While compiling a non-inlined function, the compiler cannot know if the return value will be used, and therefore it must generate it anyway. To generate and assign an object whose copy moves no more than 8 bytes has little or no cost, but to generate and assign more complex object takes time. If the temporary object owns resources, the taken time is enormously bigger, but even without allocations, the taken time grows with the number of machine words used by such object.

However, if the object to return is constructed in the return instructions themselves, therefore without assigning such value to a variable, the language standard guarantees an optimization called Return Value Optimization, that prevents the creation of temporaries.

Some compilers succeeds to avoid creating temporaries, even when the returned object is associated to a local variable (with the so-called Named Return Value Optimization), but this is not generally guaranteed and has anyway some limitations. C++ FAQ

To check whether one of the above optimizations is applied, increment a static container in every constructor, destructor, and in assignment operators of the returned object class. In case no optimization is applied, resort to one of the following alternative techniques:

  • Make void the function return type, and add to it a passed-by-reference argument, acting as return value.
  • Transform the function into a constructor of the return type, taking the same function arguments.
  • Make the function return an object of an auxiliary type, that steals the resources from the return object and passes them to the destination object, without copying their contents.
  • Use an expression template, that is an advanced technique, part of the programming paradigm called template metaprogramming.
  • If using the C++11 standard, use an rvalue reference.

Moving declarations outside loops[edit | edit source]

If a variable is declared in the body of a loop, and an assignment to it costs less than a construction plus a destruction, move that declaration before the loop.

If the variable is declared in the body of a loop, the associated object is constructed and destructed at every iteration, while if it is outside the loop, such object is constructed and destructed only once, but is presumably assigned one more time in the body of the loop.

Though, in many cases, an assignment costs exactly as much as a pair construction+destruction, and thus in such cases there is no gain in moving the declaration outside the loop and adding an assignment inside.

Assignment operator[edit | edit source]

In an assignment operator overload (operator=), if you are sure that it will never throw exceptions, copy every member variable, instead of using the copy&swap idiom.

The most efficient way to copy an object is to imitate an appropriate initialization list of a copy constructor, that is, first, to call the analogous member functions of the base classes, and then to copy every member variable, in declaration order.

Unfortunately, such technique is not exception-safe, that is if during this operation an exception is thrown, the destructors of some already constructed sub-objects could never be called. Therefore, if there is the chance that during the copy an exception is thrown, you must use an exception-safe technique, although it won't have optimal performance.

The most elegant exception-safe assignment technique is the one called copy&swap. It is exemplified by the following code, in which C represents the name of the class, and C a member function to define:

C& C::operator=(C new_value) {
    swap(new_value);
    return *this;
}

Overload to avoid conversions[edit | edit source]

To avoid costly conversions, define overloaded functions for the most typical argument types.

Let's assume you wrote the following function:

int f(const std::string& s) { return s[0]; }

whose purpose is to allows to write the following code:

std::string s("abc");
int n = f(s);

But it can be used also by the following code:

int n = f(string("abc"));

And, thanks to the implicit conversion from char* to std::string, it can be used also by the following code:

int n = f("abc");

Both the last two calls to the f function are inefficient, as they create a temporary non-empty std::string object.

To keep the efficiency of the first example call, you have to define also the following function overload:

int f(const char* s) { return s[0]; }

In general, if a function is called by passing to it an argument of an unexpected type but that can be implicitly converted to an expected type, a temporary of the expected type is created.

To avoid such temporary object, you have to define an overload of the original function that takes an argument of the type of the actual passed object, thus avoiding the need of a conversion.