简体   繁体   中英

Should a constructor for a class that contains a move-only type receive the move-only type by reference or by rvalue reference?

I recently started learning about move semantics, and I have been thinking about the following problem for a few days:

Let's say we have a non-copyable class like the following:

class Texture
{
public:

   Texture(unsigned int texID);
   ~Texture();

   Texture(const Texture&) = delete;
   Texture& operator=(const Texture&) = delete;

   Texture(Texture&& rhs);
   Texture& operator=(Texture&& rhs);

   // ...

private:

   unsigned int mTexID;
};

For those wondering, it is common to have such wrapper classes when working with OpenGL. The ID is used to access data that is stored in the GPU, and it is used to tell the GPU to destroy said data, which is done in the destructor of this wrapper class. That is why it is a non-copyable class.

Now let's say we have another non-copyable class like the following:

class Mesh
{
public:

   // Notice how the constructor receives the vector of Texture objects (a move-only type) by reference
   Mesh(const std::vector<unsigned int>& indices, std::vector<Texture>& textures)
      : mIndices(indices)
      , mTextures(std::move(textures))
   {
      // ...
   }
   ~Mesh();

   Mesh(const Mesh&) = delete;
   Mesh& operator=(const Mesh&) = delete;

   Mesh(Mesh&& rhs);
   Mesh& operator=(Mesh&& rhs);

   // ...

private:

   std::vector<unsigned int> mIndices;
   std::vector<Texture>      mTextures;
};

With the constructor the way it is right now, a client could create a Mesh by doing the following:

std::vector<unsigned int> indices;
std::vector<Texture> textures;

// ...

Mesh mesh(indices, textures); // Client is unaware that the textures vector has been moved from

My question is whether it would be better if the constructor of the Mesh class was declared like this:

// Notice how the constructor receives the vector of Texture objects (a move-only type) by rvalue reference
Mesh::Mesh(const std::vector<unsigned int>& indices, std::vector<Texture>&& textures)
   : mIndices(indices)
   , mTextures(std::move(textures))
{
   // ...
}

With this new constructor, the client would be forced to do the following when creating a Mesh object:

std::vector<unsigned int> indices;
std::vector<Texture> textures;

// ...

Mesh mesh(indices, std::move(textures)); // Client is fully aware that the textures vector has been moved from

It is certainly more typing, but now the user is fully aware that the textures vector has been moved from, and I don't see any performance implications.

So I guess my question is: are there are guidelines on what's the best way to receive move-only types that will be moved from? Receiving by reference to const clearly indicates that the type will not be moved from, so how does one do the opposite? How does one tell the client that the type will be moved from?

Using the rvalue reference is clearly superior if the value passed might be a prvalue:

struct A {};
struct B {B(A&);};
struct C {C(A&&);};
A get();
A a;

B b{a};              // OK: a is an lvalue
B b2{get()};         // error: prvalue
C c{a};              // error: lvalue
C c2{std::move(a)};  // OK: xvalue
C c3{get()};         // OK: prvalue

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM