Search code examples
c++move-semantics

Does std::move really help to save us from copying?


I fail to understand the real nature std::move

  • They say that: std::move(object) returns "an Rvalue reference to that object". Yes, I got this.
  • They also say that: use push_back(std::move(object)) instead of push_back(object) to avoid copying. I didn't get this, because it seems to contradict with the example below:
#include <utility>      
#include <iostream>     
#include <vector>       
#include <string>       
#include <iomanip>
template <class T> void print(T const & object){
    for (auto var : object){std::cout << ' '<<var;}
    std::cout << '\n';
}
int main () {
  std::string foo = "foo-string";
  std::string bar = "bar-string";
  std::vector<std::string> myvector = { "super", "vip" , "pro" , "ok" }; // capcity=size=4;

  // this block is just to increase the capacity of the vector
  myvector.push_back( "rango" );      //NOW: capacity = 8 , size =5
  std::cout << &myvector[0] <<'\n';   //check the address of the first element of the vector

  // this block is the point of the problem
  myvector.push_back(foo);                  // copies -> YES!
  myvector.push_back(std::move(bar));       // moves !?!? 

  std::cout << "myvector contains:";
  print(myvector);

  std::cout << &myvector[0] << '\n';   // re-check the address of first element of the vector = still the same.
  std::cout << "the moved-from object: " << std::quoted(bar); 

  return 0;
}
  • The address of first element of vector before and after push_back(std::move(object)) is the same, meaning the address of the last element by push_back must be stored at "a fixed distance" from the first element (in this case)
  • So if we don't copy the object to the fixed given position, then how can we store the value of that object to "that position"?

Solution

  • Let's break down what a move actually means in C++

    • For a trivial type it's the same as copying. The object will be copied byte by byte to its new location.
    • For a non-trivial type it can mean something else, depending on how the move constructor is implemented. A std::vector for example has a dynamically allocated buffer where it stores its elements. So when you move construct a std::vector from another std::vector the newly created object will take over (or steal if you will) the buffer from the old object. It will still be a new object though and have distinct memory location.

    In the case of std::string things get a little more complicated. Conceptually std::string is very similar to std::vector<char> but it has what is called a small buffer optimization (in common implementations at least). This means, that if the string is under a certain length, it will be stored within the std::string object, not in a dynamically allocated buffer. Usually the threshold is around 20 characters. Only larger strings will be placed on the free store. Because your string "bar-string" is fairly short, it will be placed within the std::string object and thus a move results in a copy of the string.