1

I'm fudging around with some code because I think I found a solution to another problem. I'm writing a little test program and I got it to work. But only if I use std::move() twice in a row.

Here's the example code:

using namespace std;

class Serializable {
public:
    virtual string serialize() = 0;
};

class Packet: public Serializable {
    string payload;
public:
    virtual string serialize() {
        return payload;
    }
    Packet(string payload): 
        payload(payload) {
            cout << "Packet made" << endl;
        }
};

class Frame: public Serializable {
    unique_ptr<Serializable> packet;
    string head;
public:
    virtual string serialize() {
        return head + packet->serialize();
    }
    Frame(string head, unique_ptr<Serializable>&& packet): 
        head(head), packet(move(packet)) { // Second Time
            cout << "Frame made" << endl;
        }
};

int main()
{
    unique_ptr<Serializable> packet = make_unique<Packet>("World!");
    Frame frame { "Hello ", move(packet)}; // First Time
    cout<<frame.serialize();

    return 0;
}

When I remove the "First one" (in main()), I get this error:

main.cpp:49:29: error: cannot bind rvalue reference of type ‘std::unique_ptr&&’ to lvalue of type ‘std::unique_ptr’

When I remove the other, I get this error:

main.cpp:39:34: error: use of deleted function ‘std::unique_ptr<_Tp, _Dp>::unique_ptr(const std::unique_ptr<_Tp, _Dp>&) [with _Tp = Serializable; _Dp = std::default_delete]’

I understand the one in the constructor, you can't copy a unique_ptr. But what about the one in main()? I'm stumped.

Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
Typhaon
  • 828
  • 8
  • 27
  • 3
    Any variable with a name is an lvalue; even if it was passed as a rvalue reference. _"..The following expressions are lvalue expressions:... the name of a variable"_ https://en.cppreference.com/w/cpp/language/value_category – Richard Critten Nov 10 '22 at 21:53
  • 2
    Perhaps [this question](https://stackoverflow.com/questions/32620750/why-are-rvalues-references-variables-not-rvalue) can shed some light on the matter? – Nathan Pierson Nov 10 '22 at 21:53
  • 1
    I hope this might help https://stackoverflow.com/questions/3413470/what-is-stdmove-and-when-should-it-be-used – Rasmi Ranjan Nayak Nov 10 '22 at 21:55
  • Ok I'm starting to get why I need it in main() I think.. But why twice? What does moving twice do? – Typhaon Nov 10 '22 at 21:59
  • 1
    @Typhaon Name `std::move` is a bit misleading, because `std::move` doesn't move anything. All it does is casting a value to rvalue reference, so that move semantics can be applied by move constructor/move assignment operator. – Yksisarvinen Nov 10 '22 at 22:01
  • 1
    If it helps, you can also think about `std::move` chain as passing a hot potato around - none of the calls holds it long enough to keep the ownership, but they move it to the next thing in the chain until it reaches its destination. Not the best analogy, but it worked for me early on. – Yksisarvinen Nov 10 '22 at 22:02
  • @Yksisarvinen Ooooh, wait it moves it to the unique_ptr move constructor again doesn't it? First from main to the Frame constructor and then to the unique_ptr constructor. Am I right? – Typhaon Nov 10 '22 at 22:04
  • 1
    Yes, and the actual move constructor is called here: `packet(move(packet))`. `Frame frame { "Hello ", move(packet)};` is just making rvalue reference, but not moving anything yet. – Yksisarvinen Nov 10 '22 at 22:07
  • 5
    `std::move` is the same as `std::you_have_my_permission_to_suck_out_my_innards_and_leave_behind_an_empty_husk` ... but shorter. – Eljay Nov 10 '22 at 22:15
  • 1
    Note there is no need for *any* declared rvalue references in this code. The ctor for `Frame` could just take `Frame(string head, unique_ptr packet)` and thereby mandate the caller provide a proper move-in context. Further, in `main`, you can eliminate `packet` entirely and just use `Frame frame{"Hello ", std::make_unique("World!")};`. Unrelated, members are initialized in type-declared order regardless of their order on the member initialization list. you should either change your initializer to set `head` second, or declare `head` before `packet` in the member decls. – WhozCraig Nov 10 '22 at 22:20
  • And, btw, `head` should be moved-in as well (e.g. `head(std::move(head))`) unless you're fond of making worthless copies. And you should also provide a default virtual destructor in `Serializable` – WhozCraig Nov 10 '22 at 22:21
  • @Yksisarvinen `st::move` does not produce an rvalue reference. rvalue references are lvalues. `move` turns rvalue references (or other lvalues) into xvalues which are not lvalues. – doug Nov 11 '22 at 05:17

1 Answers1

2

If you do:

Frame("", packet)

The compiler will try to copy the argument packet into the parameter packet. As you know, std::unique_ptr cannot be copied. Even if you are passing this into a parameter, since you cannot copy, you need to move the std::unique_ptr.

And then, in the constructor:

Frame(string head, unique_ptr<Serializable>&& packet) :
    head(head), packet(move(packet)) {
    ...
}

The std::move() here moves the constructor parameter packet to the member variable packet. Since we cannot copy, this needs to be a move operation as well.

Summary: argument packet moves into constructor parameter packet, then parameter packet moves into member variable packet. So we need to do two move operations.

Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
CoderCat
  • 101
  • 1
  • 5