【问题标题】:Why std::make_move_iterator works on vector<string> but not on vector<int>为什么 std::make_move_iterator 适用于 vector<string> 但不适用于 vector<int>
【发布时间】:2014-09-20 11:21:38
【问题描述】:

我原以为std::make_move_iterator 将始终移动内容,但似乎不是。

看起来它正在移动 vector&lt;string&gt; 中的元素,而不是 vector&lt;int&gt; 中的元素。

见下面代码sn-p:

#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&lt;int&gt;vector&lt;string&gt; 上有不同的行为吗? (或者它是一个错误?)

【问题讨论】:

  • 请注意,在您的moveStringVectorv1 输出中,移动留下的空字符串末尾有多余的空格。
  • Err...您是否认真地创建了两个函数move*Vector(),它们以完全相同的方式处理两种类型的 template 类,并且与字母 99% 相同?谁的名字甚至表明类型是唯一的区别?这让我非常困扰,以至于我感到这种敦促现在就去并将其合并到一个模板函数中;-)。我的意思是,即使输出运算符也是一个模板(与printf 不同)。唯一的区别在于可以作为数组传入的初始化。或者,实际上,使用初始化列表初始化向量。

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


【解决方案1】:

这种行为是预期的。两个向量的移动会留下原始的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,但我换一种说法:移动int 有多种有效方法,而最有效的方法是复制它。 (我将“移动”定义为从ab 以确保b 获得a 的值,并且a 保持有效状态。复制始终是移动的一种方式,即使对于@987654334 @,碰巧这对string来说并不是一个特别好的方法。)
  • @hvd 我同意。我重新表述了答案的那一部分。
  • 有没有办法真正将矢量移动(或拆分)成两部分?例如就我而言,我想获得前 5 个元素的 v1 和后 5 个元素的 v2,而不是复制它们,而是实际移动(拆分)它们。
  • @Mine 想到的最简单的方法是在移动不需要的元素后删除它们:v0.erase(v0 + 5, v0.end());
  • 好的。明白了,虽然这不是我想要的。无论如何,这就解释了为什么使用迭代器而不是容器更好。
【解决方案2】:

当我们谈论移动时,我们并不是在谈论移动对象本身(它保持原样)。被移动的是它的内部数据。这可能会也可能不会影响其内部数据被移动的对象的值。

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

重要的是要记住,std::string 内部(本质上)持有一个指向字符数组的指针。因此,当您复制 std::string 时,您复制了字符数组的每个元素。然而,move 通过复制内部指针来避免进行所有复制。

但是如果 move 操作在此处停止,这将使两个std::strings 都指向同一个字符数组,并且更改任一std::string 指向的字符数据也会更改另一个。因此,当您 移动 一个字符串时,仅仅 复制 内部指针是不够的,您必须将您移动的 std::string 的内部指针从一个点移到一个新的空白字符数组,使其不再影响其数据移动到的字符串。

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

【讨论】:

    【解决方案3】:

    移动构造函数 就像一个对象的工作方式,就像获取常规引用和移动事物的指令一样。 默认移动构造函数尝试调用所有成员变量的移动构造函数。一个用户定义的......几乎由程序员来告诉它该做什么。 您可以将对象编程为在受到移动构造函数的影响后处于未定义状态,您可以保持它们不变(析构函数仍将被调用,因此您需要处理它),您可以保持它们有效。字符串在受到移动构造函数的影响后将具有定义的状态。

    至于你的例子......

    int 可以简单地复制,它的 move 构造函数 除了复制之外什么都不做。

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-07-22
      • 1970-01-01
      • 2015-06-13
      • 2018-04-02
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多