It is sometimes claimed that C++11/14 can get you a performance boost even when merely compiling C++98 code. The justification is usually along the lines of move semantics, as in some cases the rvalue constructors are automatically generated or now part of the STL. Now I'm wondering whether these cases were previously actually already handled by RVO or similar compiler optimizations.
My question then is if you could give me an actual example of a piece of C++98 code that, without modification, runs faster using a compiler supporting the new language features. I do understand that a standard conforming compiler is not required to do the copy elision and just by that reason move semantics might bring about speed, but I'd like to see a less pathological case, if you will.
EDIT: Just to be clear, I am not asking whether new compilers are faster than old compilers, but rather if there is code whereby adding -std=c++14 to my compiler flags it would run faster (avoid copies, but if you can come up with anything else besides move semantics, I'd be interested, too)
Best Answer
I am aware of 5 general categories where recompiling a C++03 compiler as C++11 can cause unbounded performance increases that are practically unrelated to quality of implementation. These are all variations of move semantics.
std::vector
reallocateevery time the
foo
's buffer is reallocated in C++03 it copied everyvector
inbar
.In C++11 it instead moves the
bar::data
s, which is basically free.In this case, this relies on optimizations inside the
std
containervector
. In every case below, the use ofstd
containers is just because they are C++ objects that have efficientmove
semantics in C++11 "automatically" when you upgrade your compiler. Objects that don't block it that contain astd
container also inherit the automatic improvedmove
constructors.NRVO failure
When NRVO (named return value optimization) fails, in C++03 it falls back on copy, on C++11 it falls back on move. Failures of NRVO are easy:
or even:
We have three values -- the return value, and two different values within the function. Elision allows the values within the function to be 'merged' with the return value, but not with each other. They both cannot be merged with the return value without merging with each other.
The basic issue is that NRVO elision is fragile, and code with changes not near the
return
site can suddenly have massive performance reductions at that spot with no diagnostic emitted. In most NRVO failure cases C++11 ends up with amove
, while C++03 ends up with a copy.Returning a function argument
Elision is also impossible here:
in C++11 this is cheap: in C++03 there is no way to avoid the copy. Arguments to functions cannot be elided with the return value, because the lifetime and location of the parameter and return value is managed by the calling code.
However, C++11 can move from one to the other. (In a less toy example, something might be done to the
set
).push_back
orinsert
Finally elision into containers does not happen: but C++11 overloads rvalue move insert operators, which saves copies.
in C++03 a temporary
whatever
is created, then it is copied into the vectorv
. 2std::string
buffers are allocated, each with identical data, and one is discarded.In C++11 a temporary
whatever
is created. Thewhatever&&
push_back
overload thenmove
s that temporary into the vectorv
. Onestd::string
buffer is allocated, and moved into the vector. An emptystd::string
is discarded.Assignment
Stolen from @Jarod42's answer below.
Elision cannot occur with assignment, but move-from can.
here
some_function
returns a candidate to elide from, but because it is not used to construct an object directly, it cannot be elided. In C++03, the above results in the contents of the temporary being copied intosome_value
. In C++11, it is moved intosome_value
, which basically is free.For the full effect of the above, you need a compiler that synthesizes move constructors and assignment for you.
MSVC 2013 implements move constructors in
std
containers, but does not synthesize move constructors on your types.So types containing
std::vector
s and similar do not get such improvements in MSVC2013, but will start getting them in MSVC2015.clang and gcc have long since implemented implicit move constructors. Intel's 2013 compiler will support implicit generation of move constructors if you pass
-Qoption,cpp,--gen_move_operations
(they don't do it by default in an effort to be cross-compatible with MSVC2013).