c++ – When to use std :: move and why?

Question:

In what scenarios is it advisable to use std::move ?

std::vector<int> func()
{
  std::vector<int> toReturn(1000000,5);
  return std::move(toReturn); // 1
}

int main()
{
  std::vector<int> datos = std::move(func()); // 2

  // operaciones varias
}

In the example above, are the usages of std::move correct? Why? What effects can misuse of std :: move have?

Answer:

What does std::move ?

std::move converts a Lvalue a Rvalue .

What is std::move ?

C ++ 11 adds a new constructor to the catalog. Your signature would be POO(POO&&) . This constructor, if it has been defined, is invoked automatically when an Rvalue is received and its function is to transfer the state of one object to another, avoiding making a copy of it.

C ++ 11 also allows you to use a new assignment operator, whose signature would be POO& operator=(POO&&) .

These two new features can coexist seamlessly with the lifetime copy and mapping constructor. Just keep in mind that implementing the copy constructor disables the default implementation of the move constructor and vice versa. The above is applicable to the assignment operator as well.

This can be a significant performance improvement for heavy objects. An example:

class ObjetoPesado
{
  private:

    const int NumDatos = 1000000;
    int* _datos;

  public:

    ObjetoPesado()
      : _datos(new int[NumDatos])
    {
    }

    ObjetoPesado(ObjetoPesado&& origen)
      : _datos(nullptr)
    {
      std::swap(_datos,origen._datos);
    }

    ObjetoPesado(const ObjetoPesado& origen)
      : ObjetoPesado() // Llamada al constructor por defecto,
                       // soportado desde C++11
    {
      std::copy(origen._datos,
                origen._datos+NumDatos,
                _datos);
    }

    ~ObjetoPesado()
    {
      delete[] _datos;
    }
};

int main()
{
  clock_t start = clock();
  for( auto i=0; i<10000; i++ )
  {
    ObjetoPesado origen;
    ObjetoPesado* copia = new ObjetoPesado(origen); // Constructor copia
    delete copia;
  }
  std::cout << "copia: " << static_cast<double>(clock()-start) / CLOCKS_PER_SEC << std::endl;

  start = clock();
  for( auto i=0; i<10000; i++ )
  {
    ObjetoPesado origen;
    ObjetoPesado* copia = new ObjetoPesado(std::move(origen)); // Constructor move
    delete copia;
  }
  std::cout << "move: " << static_cast<double>(clock()-start) / CLOCKS_PER_SEC << std::endl;
}

Although it may seem obvious that copying a pointer from one place to another is much lighter than copying all the memory involved, it is clearer if you run the example. Running this code in release, on my machine it gives the following times:

copia: 22.929
move: 0.034

It is also clear that the move syntax is only interested in implementing it if the object makes use (direct or indirect) of dynamic memory since this improvement is applicable only to pointers.

The downside to using std::move is that the original object loses its state. Normally this should not be a problem since the invocation of these semantics is not random.

When should std :: move be used?

The move syntax, as @ Angel-Angel pointed out, can be executed automatically by calling return . This is something that should be part of the current compiler optimization catalog. The next two functions would be, in this case, equivalent.

ObjetoPesado func1()
{
  ObjetoPesado toReturn;
  return toReturn;
}

ObjetoPesado func2()
{
  ObjetoPesado toReturn;
  return std::move(toReturn);
}

But then, if std::move already invoked implicitly, when should you use it?

The most common is that this function is used above all in constructor implementations and move assignments, since we make sure that nested objects benefit from this optimization:

class POO
{
  private:
    std::string _cadena;

  public:

    POO(POO&& original)
      : _cadena(std::move(original._cadena)) // Invocamos el constructor
                                             // move de std::string
    { }
};

You also need to use it when working with std::unique_ptr . Especially when it is in containers. If we want to extract or insert a std::unique_ptr in a std::unique_ptr vector, the program will not compile directly:

int main()
{
  std::unique_ptr<int> ptr(new int(1));
  *ptr = 100;

  std::vector<std::unique_ptr<int>> datos;
  datos.push_back(ptr);            // ERROR
  datos.push_back(std::move(ptr)); // OK
  datos.push_back(std::unique_ptr<int>(new int(10)); // OK porque es un Rvalue

  std::cout << *datos[0] << std::endl;
}

What happens if the move syntax is applied to an object that does not have the move constructor implemented?

What happens is that if the move constructor is not found, the compiler will try to call the copy constructor. If this is also not found then a compile-time error will occur.

In other words, applying the move syntax on an object that is not ready for it should not have negative consequences both in terms of functionality and performance.

Once all this is explained and returning to the code of the initial question:

std::vector<int> func()
{
  std::vector<int> toReturn(1000000,5);
  return std::move(toReturn); // 1
}

int main()
{
  std::vector<int> datos = std::move(func()); // 2

  // operaciones varias
}

It is clear that the added value of the syntax in both cases is null, although it could be the case that use 1 may be necessary in those rare cases in which the compiler does not apply optimizations to the return values.

Scroll to Top