3

I'm writing a simple game engine in C++.

I'm very late to C++, because I've spent most of my free time with C, so the unique_ptr, shared_ptr and all of this ownership magic is hardly understandable for me.

I stumbled upon one thing that I can't understand at all. Let's have a following snippet:

#include <memory>
#include "vertexArray.hpp" // here VertexArray class is defined, it's implementation isn't important I think

class Mesh {
public:
    Mesh()= default;
    Mesh(std::unique_ptr<VertexArray>&& vertexArray) {
        // this->vertexArray = vertexArray; <- this doesn't work
        this->vertexArray = std::move(vertexArray); // Why is the move necessary?
    }
private:
    std::unique_ptr<VertexArray> vertexArray;
};

int main() {
    auto vArray = std::make_unique<VertexArray>(/* vertex buffer, index buffer, etc */);
    Mesh mesh = Mesh(std::move(vArray)); // <- I have to move the unique_ptr here, that I understand
}

I'm having a problem with this snippet, because I can't understand why the second std::move is necessary inside the Mesh::Mesh(std::unique_ptr<VertexArray>&&). From what I understand the assignment operator of std::unique_ptr expect rvalue at the right side. Isn't the vertexArray (which is type of std::unique_ptr<VertexArray>&&) already a rvalue?

Also, the std::move(vertexArray) isn't really clear to me, because in this example I guess the std::move will return nothing else then std::unique_ptr<VertexArray>&&, so isn't it returning exactly the same thing as vertexArray already is?

From what I understand, it is, so why do I have to call the std::move(vertexArray) where vertexArray is already a rvalue, to then convert it to... a rvalue?

y3v4d
  • 195
  • 14
  • 6
    Personally I wouldn't bother with the rvalue reference. Just use e.g. `Mesh(std::unique_ptr vertexArray)`. Then use a *constructor initializer list* to initialize `vertexArray`. Like `Mesh(std::unique_ptr vertexArray) : vertexArray{ std::move(vertexArray) } { /* Empty body */ }` – Some programmer dude Jul 26 '23 at 12:34
  • 1
    Maybe it looks like magic because you're not used to tamplates and r-value references (&&). Documentation on both can be found here [introduction to rvalue references](https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2006/n2027.html). One of the uses is to allow passing of temporary objects (without copying) – Pepijn Kramer Jul 26 '23 at 12:35
  • Unique pointers model a concept known as ownership, and as such you can't copy them. The only way to pass them on is to transfer ownership (move them) and for this you must turn them into rvalue references with std::move. So that move constructors or move assignments will be called that do the transfer of ownership. In case of std::unique_ptr this means the owner of the unique_pointer will determine when it gets deleted. A much more clear picture then you have with "raw" pointers where you kind of have to do that by documentation (with unique_ptr as you see you get compiler errors) – Pepijn Kramer Jul 26 '23 at 12:37
  • This looks very much as if your `vertexArray` member variable should be of type `VertexArray`, not of type `std::unique_ptr`. Make sure the `VertexArray` type is itself move-constructible. – bitmask Jul 26 '23 at 12:42
  • @Someprogrammerdude That was my initial go :) But then I started wandering about the &&, since I never used it, and couldn't wrap my head around why the second `std::move` is required. I accepted the answer that was put below the question, since it explained my question pretty well. – y3v4d Jul 26 '23 at 12:56
  • 2
    *... where vertexArray is already a rvalue...* Keep this rule-of-thumb in mind: if it has a name, it is **not** an rvalue. `vertexArray` is a name. – Eljay Jul 26 '23 at 13:24

1 Answers1

6

Types and value categories are two independent things in C++.

vertexArray is an lvalue-expression as it's the name of variable, even its type is rvalue-reference. You have to use std::move to convert it to rvalue-expression.

The following expressions are lvalue expressions:

  • the name of a variable, ...

std::move(vertexArray) is an xvalue-expression (rvalue-expression), as it's a function call whose return type is rvalue reference.

The following expressions are xvalue expressions:

  • a function call or an overloaded operator expression, whose return type is rvalue reference to object, such as std::move(x);
songyuanyao
  • 169,198
  • 16
  • 310
  • 405
  • I think I'm starting to get it. I had no idea that there is a differentiation between value-expression and value-reference. It makes sense, that my named variable `vertexArray` even though it's an rvalue-reference it's still lvalue expression. And I guess the assignment operator of `unique_ptr` expects the **rvalue-expression** at the right side, it doesn't care about the variable being rvalue-reference? Correct me if I'm wrong, but that makes much more sense now, thank you! – y3v4d Jul 26 '23 at 12:54
  • 1
    @y3v4d *And I guess the assignment operator of unique_ptr expects the rvalue-expression at the right side, it doesn't care about the variable being rvalue-reference?* Correct, from perspective of value category. You can just consider about types and value categories separately. – songyuanyao Jul 26 '23 at 13:01