Stumbled upon few articles claiming that passing by value could improve performance if function is gonna make a copy anyway.
I never really thought about how pass-by-value might be implemented under the hood. Exactly what happens on stack when you do smth like this: F v = f(g(h()))?
After pondering a bit I came to conclusion that I'd implement it in such way that value returned by g() is created in locations where f() expects it to be. So, basically, no copy/move constructor calls -- f() will simply take ownership of object returned by g() and destroy it when execution leaves f()'s scope. Same for g() -- it'll take ownership of object returned by h() and destroy it on return.
Alas, compilers seem to disagree. Here is the test code:
#include <cstdio>
using std::printf;
struct H
{
H() { printf("H ctor\n"); }
~H() { printf("H dtor\n"); }
H(H const&) {}
// H(H&&) {}
// H(H const&) = default;
// H(H&&) = default;
};
H h() { return H(); }
struct G
{
G() { printf("G ctor\n"); }
~G() { printf("G dtor\n"); }
G(G const&) {}
// G(G&&) {}
// G(G const&) = default;
// G(G&&) = default;
};
G g(H) { return G(); }
struct F
{
F() { printf("F ctor\n"); }
~F() { printf("F dtor\n"); }
};
F f(G) { return F(); }
int main()
{
F v = f(g(h()));
return 0;
}
On MSVC 2015 it's output is exactly what I expected:
H ctor
G ctor
H dtor
F ctor
G dtor
F dtor
But if you comment out copy constructors it looks like this:
H ctor
G ctor
H dtor
F ctor
G dtor
G dtor
H dtor
F dtor
I suspect that removing user-provided copy constructor causes compiler to generate move-constructor, which in turn causes unnecessary 'move' which doesn't go away no matter how big objects in question are (try adding 1MB array as member variable). I.e. compiler prefers 'move' so much that it chooses it over not doing anything at all.
It seems like a bug in MSVC, but I would really like someone to explain (and/or justify) what is going on here. This is question #1.
Now, if you try GCC 5.4.0 output simply doesn't make any sense:
H ctor
G ctor
F ctor
G dtor
H dtor
F dtor
H has to be destroyed before F is created! H is local to g()'s scope! Note that playing with constructors has zero effect on GCC here.
Same as with MSVC -- looks like a bug to me, but can someone explain/justify what is going on here? That is question #2.
It is really silly that after many years of working with C++ professionally I run into issues like this... After almost 4 decades compilers still can't agree on how to pass values around?