6

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!

AnInquiringMind
  • 653
  • 1
  • 6
  • 13
  • For future reference, these are useful and relevant questions: https://stackoverflow.com/questions/29089227/push-back-or-emplace-back-with-stdmake-unique and https://stackoverflow.com/questions/35404932/stdvectoremplace-back-and-stdmove. – AnInquiringMind Nov 15 '17 at 18:47

1 Answers1

5

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.

Rakete1111
  • 42,521
  • 11
  • 108
  • 141
  • Thanks so much for the response. Will go through it carefully to see if I have any follow-up questions, but for now, I have a very quick and dumb question: in my code, why was I able to simply call `move()` rather than needing to use `std::move()`? I never use `using namespace std`, and all other symbols in `std` correctly require the `std::` prefix... – AnInquiringMind Nov 15 '17 at 17:57
  • 1
    @PhysicsCodingEnthusiast That's not a dumb question! It's because `pBeta` is in the namespace. The compiler also searches the namespace of a function's arguments to find it. This is called ADL. – Rakete1111 Nov 15 '17 at 18:00
  • Ah, okay. That makes sense. Thank you. I guess, to be safe, it's probably a good idea to include `std::`, even when ADL applies? Also, one other point of confusion: in this example, what is the transfer of ownership from and to? Is it *from* the function `addBeta` and *to* the vector `betaVec`? – AnInquiringMind Nov 15 '17 at 18:09
  • 1
    @PhysicsCodingEnthusiast I do it, mainly for consistency. But is is safe, because it is an actual language rule :) I'd say that is is happening from `pBeta` to `betaVec` (or an element of it - I guess you could say both), but not from the function, because it doesn't really have anything to do with the ownership of variables defined in it. – Rakete1111 Nov 15 '17 at 18:12
  • I was initially thinking it was being transferring from `pBeta`, but it seemed strange to me, since that makes it seem like the `unique_ptr` is being transferred from itself to something else. But I suppose that `pBeta` is more accurately a named storage "container" for the `unique_ptr`, and ownership is being transferred from that "container" to the element of `betaVec`. – AnInquiringMind Nov 15 '17 at 18:18
  • Sorry, one last question for you: in this case, since the object has already been constructed, does it matter if I use `emplace_back()` vs. `push_back()`? – AnInquiringMind Nov 15 '17 at 18:21
  • @PhysicsCodingEnthusiast Yes and no. Because you are passing the object you want to construct, it doesn't matter. `emplace` is used to construct the object in place with the arguments of the constructor, but naturally it also works if you need to copy/move an object. – Rakete1111 Nov 15 '17 at 20:06