Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why std::make_move_iterator works on vector<string> but not on vector<int>

I was expecting that std::make_move_iterator will always move contents, but it seems not.

It looks like it is moving elements in vector<string> but not in vector<int>.

See the below code snippet:

#include <iostream>
#include <iterator>
#include <string>
#include <vector>

void moveIntVector()
{
  std::cout << __func__ << std::endl;
  std::vector<int> v1;
  for (unsigned i = 0; i < 10; ++i) {
    v1.push_back(i);
  }
  std::vector<int> v2(
    std::make_move_iterator(v1.begin() + 5),
    std::make_move_iterator(v1.end()));
  std::cout << "v1 is: ";
  for (auto i : v1) {
    std::cout << i << " ";
  }
  std::cout << std::endl;

  std::cout << "v2 is: ";
  for (auto i : v2) {
    std::cout << i << " ";
  }
  std::cout << std::endl;
}

void moveStringVector()
{
  std::cout << __func__ << std::endl;
  std::vector<std::string> v1;
  for (unsigned i = 0; i < 10; ++i) {
    v1.push_back(std::to_string(i));
  }
  std::vector<std::string> v2(
    std::make_move_iterator(v1.begin() + 5),
    std::make_move_iterator(v1.end()));
  std::cout << "v1 is: ";
  for (auto i : v1) {
    std::cout << i << " ";
  }
  std::cout << std::endl;

  std::cout << "v2 is: ";
  for (auto i : v2) {
    std::cout << i << " ";
  }
  std::cout << std::endl;
}

int main()
{
  moveIntVector();
  moveStringVector();
  return 0;
}

The result is:

moveIntVector
v1 is: 0 1 2 3 4 5 6 7 8 9  # I expect this should be `0 1 2 3 4` as well!
v2 is: 5 6 7 8 9 
moveStringVector
v1 is: 0 1 2 3 4      
v2 is: 5 6 7 8 9 

I'm on Ubuntu 14.04, gcc 4.8.2 and the code is compiled with -std=c++11

Could you explain why std::make_move_iterator have different behaviour on vector<int> and vector<string>? (Or is it a bug?)

like image 357
Mine Avatar asked Jul 29 '14 06:07

Mine


People also ask

What is the difference between std :: string and std :: vector?

std::string offers a very different and much expanded interface compared to std::vector<> . While the latter is just a boring old sequence of elements, the former is actually designed to represent a string and therefore offers an assortment of string-related convenience functions.

Can a vector hold strings?

Conclusion: Out of all the methods, Vector seems to be the best way for creating an array of Strings in C++.

Is STD string a vector?

From a purely philosophical point of view: yes, a string is a type of vector. It is a contiguous memory block that stores characters (a vector is a contiguous memory block that stores objects of arbitrary types). So, from this perspective, a string is a special kind of vector.

What happens when you std :: move a vector?

std::move. std::move is used to indicate that an object t may be "moved from", i.e. allowing the efficient transfer of resources from t to another object. In particular, std::move produces an xvalue expression that identifies its argument t . It is exactly equivalent to a static_cast to an rvalue reference type.


2 Answers

The behaviour is expected. A move from both vectors leaves the original v1 with 5 moved-from elements in their second half.

The difference is that when the strings are moved, what is left behind is empty strings. This is because it is a very efficient way to move strings, and leave the moved-from string in a self-consistent state (Technically, they could be left to hold the value "Hello, World, nice move!", but that would incur extra cost). The bottom line is that you don't see those moved-from strings in your output.

In the case of the int vectors, there is no way to move an int that is more efficient than copying it, so they are just copied over.

If you check the sizes of the vectors, you will see the v1 have size 10 in both cases.

Here's a simplified example to illustrate that the moved from strings are left empty:

#include <iostream>
#include <iterator>
#include <string>
#include <vector>

int main() 
{
    std::vector<std::string> v1{"a", "b", "c", "d", "e"};
    std::vector<std::string> v2(std::make_move_iterator(v1.begin()),
                                std::make_move_iterator(v1.end()));

    std::cout << "v1 size " << v1.size() << '\n';
    std::cout << "v1: ";
    for (const auto& s : v1) std::cout << s << " - ";
    std::cout << '\n';

    std::cout << "v2 size " << v2.size() << '\n';
    std::cout << "v2: ";
    for (const auto& s : v2) std::cout << s << " - ";
    std::cout << '\n';
}

Output:

v1 size 5
v1:  -  -  -  -  - 
v2 size 5
v2: a - b - c - d - e - 
like image 113
juanchopanza Avatar answered Oct 18 '22 20:10

juanchopanza


When we talk about a move we are not talking about moving the object itself (it remains intact). What gets moved are its internal data. This may or may not affect the value of the object whose internal data gets moved.

That is why your int array doesn't loose its original ints. As to your string example, it still has the original std::strings just like the int example but their internal values have changed to empty strings.

It is important to remember that internally a std::string (essentially) holds a pointer to a character array. So when you copy a std::string you copy every element of the character array. A move, however, avoids doing all that copying by copying the internal pointer instead.

But if the move operation stopped there that would leave both std::strings pointing at the same character array and changing the character data pointed to by either std::string would also change the other's. So when you move a string it is not enough to merely copy the internal pointer, you have to make the internal pointer of the std::string you moved from point to a new blank character array so that it can no longer affect the string its data was moved to.

When moving an int there is no further action required after the copy of its data. There are no pointers involved so after the copy both ints contain independent data.

like image 7
Galik Avatar answered Oct 18 '22 20:10

Galik