2

Can someone please explain me one thing. From the one side the move constructor was designed to optimize the memory & processor usage by eliminating unnecessary copying an objects BUT from other side almost everywhere the move constructor is going to be used the compiler uses copy elision, disabling the usage of the move ctor? Isn't it irrational?

Jan Schultke
  • 17,446
  • 6
  • 47
  • 96
Derek81
  • 145
  • 9
  • 3
    _"almost everywhere the move constructor is going to be used the compiler uses the copy elision"_ — That's totally wrong. (For instance, try to insert an rvalue into a container via rvalue reference.) – Daniel Langr Sep 01 '20 at 08:03
  • 1
    @Derek81 You'll have to back up your claim by a [mcve]. – Ted Lyngmo Sep 01 '20 at 08:06
  • 2
    The "move constructor" could also be called the "*rvalue* constructor". – Some programmer dude Sep 01 '20 at 08:06
  • @DanielLangr No, it's TOTALLY TRUE. look for eg. at: https://stackoverflow.com/questions/13099603/c11-move-constructor-not-called-default-constructor-preferred. There are many times when the move operations is omitted by using the copy elision. – Derek81 Sep 01 '20 at 08:07
  • 4
    @Derek81 And there are many when it isn't. – Daniel Langr Sep 01 '20 at 08:08
  • @DanielLangr. Can You please list me the cases when move ctor is ALWAYS used (no elision), excluding the explicit move semantic usage like "std::move"> – Derek81 Sep 01 '20 at 08:14
  • 1
    @Derek81 doing a ctor + copy elision, rather than ctor + move constructor is cheaper. Why should the compiler not do that if it is possible? Move construction creates a new object to which the data of the old object is _"moved"_ (in the worst case if all data of the source object is fully enclosed in it, it is as expensive as a regular copy) a move constructor has only benefited over a copy constructor if you have member variables that can be swapped like pointers, or containers that support swapping (or if you want to make the object not copy but only moveable) – t.niese Sep 01 '20 at 08:16
  • @Derek81 Why "excluding the explicit move semantics"? Here is live demo where move constructors isn't elided: https://godbolt.org/z/Gxj11P. – Daniel Langr Sep 01 '20 at 08:16
  • @DanielLangr Just because it's obvious cases - when You explicitly call move f-ction. I'm only interested in implicit cases. – Derek81 Sep 01 '20 at 08:26
  • @Derek81 You asked whether move constructor has any meaning when it's mostly elided. If you are interested only in implicit cases, that question does not make sense anymore. It's a different question, such as: Are there any cases where move constructor isn't elided when it's invoked implicitly? Even if they weren't, it wouldn't prove that move constructors has no use in practice. – Daniel Langr Sep 01 '20 at 09:08

4 Answers4

5

There are plenty of cases where the move constructor will still get called and copy elision is not being used:

// inserting existing objects into a container
MyObject myobject;
std::vector<MyObject> myvector;
myvector.push_back(std::move(myobject));

// inserting temporary objects into a container
myvector.push_back(MyObject());

// swapping
MyObject other;
std::swap(myobject, other);

// calling functions with existing objects
void foo(MyObject x);

foo(std::move(myobject));

... and many more.

The only instance where there is mandatory copy elision (since C++17) is when constructing values from the result of a function call or a constructor. In such cases, the compiler isn't even allowed to use the move constructor. For example:

MyObject bar() {
    return MyObject();
}

void example() {
    MyObject x = bar(); // copy elision here
    MyObject y = MyObject(); // also here
}

In general, the purpose of copy elision is not to eliminate move construction alltogether, but to avoid unnecessary constructions when initializing variables from prvalues.


See cppreference on Copy Elision.

Jan Schultke
  • 17,446
  • 6
  • 47
  • 96
  • Ok, but i've completely disabled the optimization (set Optimization to Disabled (/Od) in my VS2019) and move ctor still isn't called in Your's mandatory copy elision example! – Derek81 Sep 01 '20 at 09:00
  • Can You please answer my last question? – Derek81 Sep 01 '20 at 09:07
  • @Derek81 do you mean the second code block? That code block is an example of where copy elision is **mandatory**, so the compiler isn't even allowed to use the move constructor there. The **first** code block shows examples of where copy elision **won't** happpen. – Jan Schultke Sep 01 '20 at 09:11
  • @Derek81 Note that even since C++17, copy elision is typically applied by compilers with **disabled** optimizations as well (`-O0`). GCC, for instance, allows you to disable copy elision here, but you have to use a special flag `-fno-elide-constructors`. Don't know about MSVC. – Daniel Langr Sep 01 '20 at 09:14
  • @Jan Schultke Ok i understand.But another case: calling fun() where fun: `fun () {return MyObject() }` also doesn;t call the move ctor? – Derek81 Sep 01 '20 at 09:15
  • @Derek81 yes, this is analogous to the `bar()` function in the second code sample. Since C++17, copy elision is also mandatory in this case. `MyObject()` is a `prvalue` when returned from `bar()`, so the compiler can elide one construction. – Jan Schultke Sep 01 '20 at 09:17
1

Here is a simple example where move is called. It is a toy example for which the rule of zero could have been relevant, but assume that there are also other members inside the class that require going with the rule of five.

class A {
    std::string s;
public:
    A(const char* s = ""): s(s) {}
    ~A() {}
    A(const A& a): s(a.s) {
        std::cout << "copy ctor" << std::endl;
    }
    A& operator=(const A& a) {
        s = a.s;
        std::cout << "copy assignment" << std::endl;
        return *this;
    }
    A(A&& a): s(std::move(a.s)) {
        std::cout << "move ctor" << std::endl;
    }
    A& operator=(A&& a) {
        s = std::move(a.s);
        std::cout << "move assignment" << std::endl;
        return *this;
    }
};

int main() {
    A a;
    a = "hi"; // move
    // suppose we KNOW here that a is not needed anymore
    A a2 = std::move(a); // move
    a = "bye"; // move
}

Code: http://coliru.stacked-crooked.com/a/97d25c43e0edb00b

Amir Kirsh
  • 12,564
  • 41
  • 74
0

Because copy elision has limits, compiler must know the lifetime of an object to predict whether copy elision can be done. For example:

std::vecter<MyObj> v;
v.push(MyObj()); // compiler has a higher chance to do the copy elision

but consider this:

MyObj my_obj;
v.push(my_obj)
// ...
// my_obj will never use

in this case, the compiler won't know that the my_obj will be never use, so the normal copy will performed. If efficiency matters, you have to use v.push(std::move(my_obj)); to explicit tell the compiler that "I will never use my_obj again"

0

move constructor was designed to optimize the memory & processor usage by eliminating unnecessary copying an object

That is not true. A move construction creates a new object to which the data of the old object is "moved" (in the worst case if all data of the source object is fully enclosed in it, it is as expensive as a regular copy) a move constructor has only benefited over a copy constructor if you have member variables that can be swapped like pointers or containers that support swapping (or if it holds resources that can't be copied)

So copy elision is always desired over a move ctor. But that does not mean that a move ctor does not have any use. In many cases, however, a move ctor is just syntactic sugar over swap and reset/empty/destruct (not exactly true, but closely).

Besides the swap case, a move ctor is also useful for things that are or should not be copyable and for which you don't want to use pointers. e.g. a std::uniqu_ptr should not be copyable because of the unique ownership, but you might want to pass the ownership while calling a function, so moving its resources is important.

You can see move sematic as a standardized process, so that copy elision if possible, and if it is not a fallback to move ctor.

t.niese
  • 39,256
  • 9
  • 74
  • 101