3

Consider this code:

#include <iostream>

using namespace std;

struct Foo {
public:
    int _a{};
    Foo(int a) : _a{a} 
    {
        std::cout << "ctor" << std::endl;
    }

    Foo(const Foo &)
    {
        std::cout << "copy" << std::endl;
    }
};

int main () {
    Foo a{10};
    Foo b = 10;
    std::cout << b._a << std::endl;
}

When I compile with

g++ -std=c++11 -fno-elide-constructors test.cpp

the output is

ctor ctor copy 0

which is what I expect, since the in Foo b = 10, 10 is implicitly constructed from int, then b is copy constructed from Foo. Furthermore, my copy constructor doesn't do anything, so the member _a remains 0 (as it is in-class-initialized).

However, when I use copy elision

g++ -std=c++11 test.cpp

the output is

ctor ctor 10

which is surprising to say the least. I understand that the copy constructor is elided here, but this is a serious side-effect (the fact that the member is once initialized to 0 and once to 10), as it affects the rest of the code path.

Is this behaviour normal?

vsoftco
  • 55,410
  • 12
  • 139
  • 252
  • 7
    Surprising **why**, exactly? It’s entirely conforming (and, yes, expected) behaviour – after all, the whole point of copy elision is to, well, elide the call to the copy constructor. – Konrad Rudolph Feb 22 '15 at 16:03
  • 3
    Of course it is normal. If copy elision had to follow the as-if rule it wouldn't be a thing. It would just be like any other permissible optimization. – juanchopanza Feb 22 '15 at 16:04
  • @KonradRudolph, juanchopanza, yes, now I understand that it doesn't follow the as-if rule, I was under the false impression that it must – vsoftco Feb 22 '15 at 16:06
  • 2
    @vsoftco: I might add that the only way this could ever hit you was when you wrote a broken copy constructor to begin with, i.e. one that doesn't actually copy. – Christian Hackl Feb 22 '15 at 16:07
  • 2
    This is what copy elision _is_.... – Lightness Races in Orbit Feb 22 '15 at 16:10
  • @LightnessRacesinOrbit yes I understand now... It's described even here: http://en.cppreference.com/w/cpp/language/copy_elision but for some reason I was 100% convinced that there cannot be side effects (and of course I was wrong). – vsoftco Feb 22 '15 at 16:11
  • 2
    @ChristianHackl "hit you" may not be the right word, but... It's not uncommon to instrument copy constructors as he does, which definitely results in visible side effects (but ones which shouldn't remain in delivered code---such instrumentation is really only for debugging). Another situation is when a class contains a reference count; the copy constructor should _not_ copy the reference count from the source. (Of course, most classes that contain a reference counter probably shouldn't support copy.) – James Kanze Feb 22 '15 at 16:44
  • @JamesKanze what about std::shared_ptr ? How does it circumvent this issue? – vsoftco Feb 22 '15 at 17:14
  • 1
    @vsoftco `std::shared_ptr` doesn't use invasive reference counting, which means that the objects it points to don't contain a reference count. Which means that it can be used with any objects, but also makes it extremely dangerous to use. – James Kanze Feb 23 '15 at 18:00
  • 1
    @vsoftco `std::shared_ptr` doesn't contain a ref count, it has access to a shared ref count. – curiousguy Aug 26 '15 at 01:01

1 Answers1

4

The whole point of singling copy elision out as a on optimization in specific cases is to allow eliding side effects of copy construction. That is, yes, it expected that copy elision happens despite the copy constructor and/or the destructor having side effects.

If you don't want copy elision to happen in certain cases you'll need to arrange for it to be inhibited. In your specific case it is admittedly a bit annoying to inhibit copy elision but something like this should do the trick:

template <typename T>
T const& inhibit(T const& ref) {
    return ref;
}
// ...
Foo b = inhibit<Foo>(10);
Dietmar Kühl
  • 150,225
  • 13
  • 225
  • 380
  • 10
    @vsoftco You should really design your code assuming copy elision can happen instead of trying to prevent it. – juanchopanza Feb 22 '15 at 16:06
  • @juanchopanza yes I know :) this was something I came up with trying to understand how exactly copy elision works – vsoftco Feb 22 '15 at 16:07
  • Dietmar why cannot the compiler perform copy elision when the object is constructed via an additional layer, like in your example above? – vsoftco Feb 22 '15 at 16:13
  • 2
    @vsoftco: copy elision is only allowed in a few specifically specified situations. Going from memory these are copying a temporary, returning a named object, throwing a named object, and I thinks something else I keep forgetting. However, eliding a copy from a reference returned by a function isn't one of the cases. – Dietmar Kühl Feb 22 '15 at 16:19