I like to std::move it

A little while ago while I was surfing the web I stumbled on a blog of a C++ programmer. Sadly, I was unable to retrace my steps, so I will have to reconstruct what I found there from memory. The programmer was faced with the task of multiplying two matrices with each other. The result is yet another matrix. Efficiency and ease of use were important criteria. Here is the relevant part of the code:

matrix & matrix::operator* (matrix const & other) const
{
	// snip: ... assert matrix sizes are compatible ...
	matrix * result = new matrix(rows(), other.columns());
	// snip: ... compute and store matrix elements ...
	return *result;
}

Let us put on our CSI C++ hat for a while.

Why the code is like it is

Apparently, the programmer was aware of a few things. He was aware that copying a matrix should be avoided, since they are quite often very large. Thus, the function cannot return a matrix object. He thought that returning a pointer is unacceptable, since the user would have to take care of dereferencing and stuff. A reference is as convenient to use as a copy, but does not copy any data, so this might explain the return type. Finally, the programmer knew that local variables are destroyed when they go out of scope. Variables created on the heap, i.e., created with new, escape this fate. So above code is a convenient and efficient solution to the multiplication problem. And it leaks memory. Oops!

Inspecting the memory leak

Above snippet leaks memory because for the new there is no matching delete. To be fair, there is a way to use the code correctly:

matrix A; // snip: filling A with valuesthe 
matrix B; // snip: filling B with values

matrix & C = A * B;
delete (&C); // call delete with address of matrix C

For chained multiplications (A * (B * C)) memory will be lost. In case of exceptions delete might not get called at all. Users may—and will—forget to call delete. This is why the sample code is dangerous and must be replaced by something else.

Moving the result

Let us modify the code example a little by introducing std::move:

#include <utility>

matrix matrix::operator* (matrix const & other) const
{
	// snip: ... assert matrix sizes are compatible ...
	matrix result(rows(), other.columns());
	// snip: ... compute and store matrix elements ...
	return std::move(result);
}

If one would return the result with return result;, the compiler would actually return a new copy of matrix. The copy is created by the copy constructor:

// declaration of copy constructor
matrix::matrix(matrix const & source);

The useful utility function std::move instead triggers that the returned matrix object is constructed with a different constructor, a so-called move constructor:

// declaration of move constructor
matrix::matrix(matrix && source);

Adding a move constructor

This move constructor may modify source as it pleases, as long as source remains in a valid state (i.e., it can be destroyed without problems). If no special move constructor exists, return std::move(result); will call the copy constructor. Move constructors are already provided for the containers in C++11’s standard template library. Hence, the matrix class can be easily extended with a move constructor:

class matrix {
public:
	matrix(matrix && source);
	// snip: ... other member functions ...
private:
	std::size_t columns_;
	std::size_t rows_;
	std::vector<double> elements_;
};

matrix::matrix(matrix && source) :
	columns_(source.columns_),
	rows_(source.rows_),
	elements_(std::move(source.elements_))
{
}

Conclusion

All in all, the use of std::move in conjunction with a move constructor allowed us to write clean, efficient, and safe code. In case of exceptions, the destructor of matrix will automatically be called. No user will ever have to think about how to use our code correctly. All she has to keep in mind is that matrix multiplication is not commutative…

12 Replies to “I like to std::move it”

  1. Hi, thanks for the really nice blog!

    Let me just verify that I got the synapsis right: The type must have a move constructor AND I need to use std::move for the move-constructor to be utilized, correct?

    1. Thanks Matthias!

      You understood the post correctly. To avoid a copy, you need to use std::move and provide a move constructor. If you use std::move, but do not provide a move constructor, a copy will be returned. If you return a variable defined prior to the return statement, a copy will be returned as well, even if a move constructor is provided.

      In some situations, though, a move constructor is implicitly used. For example, this is the case if you return a newly constructed object without a name, such as return std::vector(5). In this case, the move constructor of std::vector is used.

  2. Hi,
    If I get the whole thing correctly, in the “move” solution, you use the template move constructors, to move items from one container into an other. I think this is done by coping pointers ? I’m not sure… but in this certain case I think that matrix is made from Integers ? In this case I think that copy would not cause overhead ? As copy pointers maybe the same time as copy integers. Of course if you keep anything bigger in your container, than move is definitely a better way !
    Am I right, or I don’t consider something ?
    Thanks in advance !
    Peter

    P.S.: Great site ! I like your articles.

    1. Hi Peter!

      Thanks for your comment! You are right in that moving is often achieved by copying pointers. When you look at the “Adding a move constructor” section, you see that matrix contains three members; two std::size_ts and a std::vector<double>. There is little point in moving size_ts, since as you explained copying a pointer is the same work as copying an integer.

      There is, however, much to be gained by moving the vector which holds all elements of the matrix. Internally, vector consists of some integers (to hold the current size and capacity, for example) and a single pointer to the memory location which contains all values. vector's move constructor will copy this pointer, not the memory location, and this is where the efficiency gain comes from.

      Hope this answers your question!

      Cheers, Michael

  3. Would you mention RVO in context of the above article? I understand std::move gives a control about the whole procedure so one doesn’t have to rely on compiler doing it (or not), but it would be nice to show the alternative.

    1. Hi Aleksander!

      Sorry for taking so long for the reply, your post deserves better. Return value optimization (RVO) seems like a rather complicated compiler optimization (at least for non-compiler builders), and to understand it completely would require a dedicated article.

      I have the feeling, though, that std::move is easier to understand. Furthermore, it is more deterministic since any compiler with support for rvalue semantics will do the move optimization no matter which compiler flags are active. For me, std::move seems like the way to go.

      1. Michael,

        you’re absolutely right about std::move being the way to go with returning values from functions and should be used whenever it can be. I was just suggesting you to mention, that not all is lost when one does not use it and that it’s not a new concept – just new and proper way of doing it.

        And yes, one could probably write a book (not just an article) about RVO 🙂

        1. Hello Aleksander,

          I might add something. Move semantics is not so much an optimization of copying, as copying is a pessimization of move. This means move is not only RVO but also allows us to do new things. E.g. to transfer ownership of threads, filehandles and other non-copyable objects without wrapping them into pointers first.

  4. I’m confused. I thought returning a *local* by value carried an implicit move anyway, since the local would be treated as rvalue as it’s going out of scope anyway.

    So what I though was there there never is a need to invoke [return std::move(local_value);] because the language already takes care of that … ???

    1. Hello Martin,
      I think you are not confused, but simply found an error in the above article:
      ‘If one would return the result with return result;, the compiler would actually return a new copy of matrix.’ This statement is wrong and you are right about the language applying implicit move semantics for local variables. Michael and I were not aware of this. Thanks for pointing this out! Now of course we need to fix the article…, and our codebase…

    2. Hello Martin!

      You seem to know the standard pretty well, I should hire you as a reviewer ;-). I did some digging in the standard draft and found this example in §12.8 (32):

      class Thing {
      public:
              Thing();
              ~Thing();
              Thing(Thing&&);
      private:
              Thing(const Thing&);
      };
      
      Thing f(bool b) {
              Thing t;
              if (b)
                      // OK: Thing(Thing&&) used (or elided) to throw t
                      throw t; 
              // OK: Thing(Thing&&) used (or elided) to return t
              return t; 
      
      }
      
      // OK: Thing(Thing&&) used (or elided) to construct t2
      Thing t2 = f(false); 
      

      I think this proves pretty well that you are right and I have to fix my article. Thank you very much for pointing this out, I am always happy to learn!

Leave a Reply

Your email address will not be published. Required fields are marked *