4

Consider the following code snippet:

#include <iostream>
#include <string>

class A { 
 public:
    A() { 
        std::cout << "A::A()\n";
    } 

    ~A() { 
        std::cout << "A::~A()\n";
    } 

    A(const A&) = delete;

    A(A&&) { 
        std::cout << "A::A(A&&)\n";
    };

};

A f() { 
    A a;
    return a;
}

int main() { 
    A a = f();
    return 0;
}

It compiles fine with g++ and clang++ and output is

A::A()
A::~A()

It seems like RVO kicks in in that case. Note that no move constructor is being called.

However, if one will remove that non-used move constructor from the code above and snippet turns into this:

#include <iostream>
#include <string>

class A { 
 public:
    A() { 
        std::cout << "A::A()\n";
    } 

    ~A() { 
        std::cout << "A::~A()\n";
    } 

    A(const A&) = delete;

};

A f() { 
    A a;
    return a;
}

int main() { 
    A a = f();
    return 0;
}

Both clang++ and g++ refuse to compile this because of copy-constructor of class A is marked as deleted, so it seems that no RVO takes place.

How removing non-used move constructor could lead to this?

Anton K
  • 670
  • 6
  • 19

3 Answers3

4

Note that in copy elision optimization, the copy/move constructor still has to be present and accessible. And it's not guaranteed that copy elision will be performed in each case.

(emphasis mine)

Even when copy elision takes place and the copy-/move-constructor is not called, it must be present and accessible (as if no optimization happened at all), otherwise the program is ill-formed.

For your code, the copy constructor has been deleteed, and if you remove the definition of move constructor, and it won't be implicitly declared because class A has a user-defined destructor, then both of move/copy constructor are not present and accessible, that's why compile failed.

In summary, the copy/move syntax is needed here and compiler will check for it. Then compiler will decide to perform copy elision or not (e.g. in debug mode).

BTW: You can use -fno-elide-constructors with clang and gcc to prohibit it.

songyuanyao
  • 169,198
  • 16
  • 310
  • 405
  • I don't get how compiler chooses what constructor (copy or move) to use to return local from function before applying rvo. Is that defined? – Anton K Jun 01 '16 at 16:22
  • @Anton Compiler is using the best match. If the value is an rvalue, and move ctor is availale, it is going to be called. If it is not an rvalue, or move constructor is not available, copy ctor will be used. In that regard, it is not different from overload resolution for regular functions. – SergeyA Jun 01 '16 at 16:24
  • @Anton Yes which one will be picked up is defined well. For your code, move ctor will be selected because `f()` will return a temoprary which could be bound to rvalue reference. Anyway it seems to be aonther problem. – songyuanyao Jun 01 '16 at 16:31
3

You need to keep in mind that (N)RVO is an optimization. Even if it kicks in, the code should be conforming to standard, which says that value is constructed using copy (or move) constructor. Even if constructor is not eventually called, it has to be available.

There is a proposal to allow for missing/unavailable constructors if it is not going to be called due to optimizations, but I doubt it will be implemented.

SergeyA
  • 61,605
  • 5
  • 78
  • 137
  • Does that mean that in the first snippet local `a` from function `f` is being returned by move-semantics and then optimizer decides to use rvo and throw call to move-constructor away? – Anton K Jun 01 '16 at 16:14
  • It is hard to say what happens first and second in the compiler :) But the code has to be correct first, and optimized second. – SergeyA Jun 01 '16 at 16:22
  • Let's rephrase: first code snipped is correct, because move ctor is defined and accessible, which allows to return local `a` from `f`? – Anton K Jun 01 '16 at 16:25
  • @Anton, that's correct. Returning from local values (or arguments) treats them as temporaries. – SergeyA Jun 01 '16 at 16:28
-3

If you have copy constructor you also must have move constructor if i recall correctly.