Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is move necessary with emplace_back in this example?

The following minimal working example compiles when the code under option 1 or option 2 is used, but does not compile when the code under option 3 is used. I was under the assumption that emplace_back() implicitly uses/calls a move constructor, so why is an explicit move() necessary? Does it have something to do with r-value vs. l-value? Or does this have to do with std::unique_ptr needing to transfer ownership? (I am still new to these concepts, particularly in this context.)

For completeness, option 4 with push_back() does not compile, either, unless move() is called.

#include <iostream>
#include <vector>
#include <memory>

class Beta {
public:
    Beta(int x, int y, int z): mX(x), mY(y), mZ(z) { };
    int mX; int mY; int mZ;

};

class Alpha {
public:
    std::vector<std::unique_ptr<Beta>> betaVec;
    void addBeta(int x, int y, int z) {
        // only choose one of the following options:
        // option 1 (compiles)
        std::unique_ptr<Beta> pBeta = std::make_unique<Beta>(x, y, z);
        betaVec.emplace_back(move(pBeta));

        // option 2 (compiles)
        betaVec.emplace_back(std::make_unique<Beta>(x, y, z));

        // option 3 (does not compile)
        std::unique_ptr<Beta> pBeta = std::make_unique<Beta>(x, y, z);
        betaVec.emplace_back(pBeta);

        // option 4 (does not compile)
        std::unique_ptr<Beta> pBeta = std::make_unique<Beta>(x, y, z);
        betaVec.push_back(pBeta);

        // option 5 (compiles)
        std::unique_ptr<Beta> pBeta = std::make_unique<Beta>(x, y, z);
        betaVec.push_back(move(pBeta));
    }
};

int main() {

    return 0;
}

Note: I do not believe that this is a duplicate of this question about passing unique_ptr parameters to functions, even though the answers to the linked question are useful, as this is asking about defining a unique_ptr within a function and then moving it to a member vector so that it is not destroyed at the end of the function and, furthermore, is asking specifically about emplace_back() in this context.

Additionally, I think that it would be useful to have explanations given in this context, as it is sometimes difficult to translate explanations from one context to another. Thank you!

like image 853
AnInquiringMind Avatar asked Nov 15 '17 17:11

AnInquiringMind


1 Answers

I was under the assumption that emplace_back() implicitly uses/calls a move constructor

Sorry, but you're assumption is wrong. emplace_back constructs the object in the vector in-place, i.e. instead of copying/moving the object from its parameters, it constructs the element directly which avoids the copy/move constructor.

Now, if you construct the object with the same (but another) object, then of course either the copy or the move constructor will get used instead, which is what is happening in your case.

so why is an explicit move() necessary

Because you can't copy a std::unique_ptr. Basically, emplace_back does something akin to this:

new (place) T(std::forward<Ts>(args)...);

It's like if you did: T a(std::forward<Ts>(args)...) (for construction only, it doesn't actually do the same thing).

Now it might be a bit more obvious:

T option1(std::move(pBeta)); // ok, move
T option3(pBeta); // error, copy

Does it have something to do with r-value vs. l-value? Or does this have to do with std::unique_ptr needing to transfer ownership?

Well, in a way, yes. std::unique_ptr requires explicit transfer of ownership, that's why the copy is disabled and the move is not (you still want to transfer ownership! And a copy can happen everywhere - why std::auto_ptr was deprecated, then removed). An rvalue uses move semantics by default, while an lvalue does not. By using std::move, you are doing a conversion from an lvalue to a prvalue, effectively "hiding" the fact that you have an lvalue, and the compiler will happily move from it.

like image 133
Rakete1111 Avatar answered Oct 22 '22 13:10

Rakete1111