Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Hidden move-construction

Tags:

c++

c++11

Why sometimes doesn't move-constructor call? Testing move-semantics (Live code):

struct Test {
    int id;
    Test(int id) : id(id) {
        cout << id << "  Test() " << endl;
    }
    ~Test() {
        cout << id << "  ~Test() " << endl;
    }
    Test(const Test &t) : id(t.id) {
        cout << id << "  Test(const Test &t) " << endl;
    }
    Test(Test &&t) : id(t.id) {
        cout << id << "  Test(Test &&t) " << endl;
    }
    Test &operator=(const Test &t) {
        cout << id << "  operator=(const Test &t) " << endl;
        return *this;
    }
    Test &operator=(Test &&t) {
        cout << id << "  operator=(Test &&t) " << endl;
        return *this;
    }
};

void f(Test z) {
    cout << z.id << "  f(Test z) " << endl;
}

int main() {
    f(Test(1));

    Test t(2); f(t);
}

Output:

1  Test() 
1  f(Test t)               <---// where is move constructor ?!
1  ~Test() 
2  Test() 
2  Test(const Test &t)     <---// copy constructor of t(2)
2  f(Test t) 
2  ~Test() 
2  ~Test()

Test shows copy-constructor is invoked.

But, after f(Test(1)); function f was called without invoking move-constructor for rvalue object of Test(1).

Is it an implicit compiler optimization? or I missed an important point?

like image 835
masoud Avatar asked Feb 16 '23 02:02

masoud


1 Answers

The compiler is explicitly allowed to elide copies (or moves) of temporary objects. Basically, the object is constructed in the place where the effective result is expected. This elision is even allowed if the constructor or the destructor have side effects.

The relevant clause is 12.8 [class.copy] paragraph 31:

When certain criteria are met, an implementation is allowed to omit the copy/move construction of a class object, even if the constructor selected for the copy/move operation and/or the destructor for the object have side effects. In such cases, the implementation treats the source and target of the omitted copy/move operation as simply two different ways of referring to the same object, and the destruction of that object occurs at the later of the times when the two objects would have been destroyed without the optimization. This elision of copy/move operations, called copy elision, is permitted in the following circumstances (which may be combined to eliminate multiple copies): ...

The cases where copy elision can be used are basically

  1. In return statements when returning a temporary or a local variable.
  2. In throw expressions when throwing a temporary or a local variable.
  3. When a temporary object would be copied.
  4. When catching an object by value.

The exact conditions under which the copy can be elided are listed in 12.8 [class.copy] paragraph 31.

The easiest approach to prevent copy/move elision is to pass it through a function which returns a suitable reference, e.g., using

f(std::move(Test(1)));

should prevent the move elision.

like image 139
Dietmar Kühl Avatar answered Feb 19 '23 15:02

Dietmar Kühl