为什么std :: make_move_iterator适用于vector <string>但不适用于vector <int> </int> </string>

时间:2014-07-29 06:27:04

标签: c++ string c++11 vector move-semantics

我希望std::make_move_iterator始终移动内容,但似乎没有。

看起来它正在移动vector<string>中的元素,而不是vector<int>中的元素。

请参阅以下代码段:

#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;
}

结果是:

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 

我在Ubuntu 14.04, gcc 4.8.2上,代码编译为-std=c++11

您能否解释为什么std::make_move_iteratorvector<int>vector<string>上有不同的行为? (或者它是一个错误?)

3 个答案:

答案 0 :(得分:18)

预期会有这种行为。从两个向量的移动将原始v1从下半部分中移出5个元素。

不同之处在于,当移动字符串时,留下的是空字符串。这是因为它是一种非常有效的方式来移动字符串,并使移动的字符串处于自洽状态(从技术上讲,它们可以保留值"Hello, World, nice move!",但这会产生额外的成本) 。最重要的是,您不会在输出中看到那些移动的字符串。

对于int向量,无法移动比复制它更有效的int,因此只需复制它们。

如果检查向量的大小,在两种情况下都会看到v1的大小为10。

这是一个简单的例子,用于说明从字符串移动的空白:

#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';
}

输出:

v1 size 5
v1:  -  -  -  -  - 
v2 size 5
v2: a - b - c - d - e - 

答案 1 :(得分:3)

当我们谈论移动时,我们不是在谈论移动对象本身(它仍然完好无损)。移动的是它的内部数据。这可能会也可能不会影响内部数据移动的对象的值。

这就是为什么您的int数组不会丢失其原始int的原因。至于您的字符串示例,它仍然具有原始std::strings,就像int示例一样,但它们的内部值已更改为空字符串。

重要的是要记住,std::string(本质上)内部保存一个指向字符数组的指针。因此,当您复制 std::string时,您会复制字符数组的每个元素。但是,移动可以避免通过复制内部指针来完成所有复制。

但是如果 move 操作停止在那里会使两个std::string指向同一个字符数组并且更改std::string指向的字符数据也会改变其他人。所以,当你移动一个字符串时,仅仅复制内部指针是不够的,你必须使你从点移动的std::string的内部指针一个新的空白字符数组,以便它不再影响其数据被移动到的字符串。

移动int时,复制数据后无需进一步操作。没有涉及指针,因此在复制后两个整数都包含独立数据。

答案 2 :(得分:0)

移动构造函数就像一个对象,就像一个普通的引用和一条指令来移动事物。 默认移动构造函数 尝试调用所有成员变量的移动构造函数。一个用户定义的...几乎由程序员来告诉它做什么。 您可以将对象编程为在受移动构造函数约束后处于未定义状态,您可以保持它们不变(仍然会调用析构函数,因此您需要注意这一点),您可以保持它们有效。字符串在受制于移动构造函数后将具有定义的状态。

至于你的例子...

int 是可简单复制的,它的 移动构造函数 除了复制之外什么都不做。

string 是不可复制的。它有一些动态的东西,移动构造函数移动。并且前一个的长度为零,您正在打印它们,以及您添加的尾随“空格”。只是它们是最后 5 个元素,在您打印的末尾,您没有注意到它,因为它相当于 5 个尾随空格。