【问题标题】:Append a copy of std::vector to the end of itself [duplicate]将 std::vector 的副本附加到自身的末尾[重复]
【发布时间】:2014-02-15 19:49:32
【问题描述】:

我正在尝试复制一个字符串向量并将其附加到其原始向量的末尾,即复制其内容。示例:

 Input : vector<string> s = {"abc", "def"}
 Output: vector<string> s = {"abc", "def", "abc", "def"}

我使用的是插入方法,即

s.insert(s.end(), s.begin(), s.end());

但是,这显示了依赖于编译器的结果。在,LLVM 铿锵声中,它给了我预期的答案。

有了 GCC,它给了我

 Output: vector<string> s = {"abc", "def", "", ""}

我想知道为什么会发生这种情况以及实现此向量复制目标的最安全方法是什么?

这是上述程序的 ideone.com 链接:http://ideone.com/40CH8q

【问题讨论】:

  • 如果迭代器无效,它看起来像 UB。致电reserve 以保证不会失效。
  • 您使用的是哪个版本的 g++?使用 g++ 4.8.1,我得到了正确的输出。
  • 我使用的是相同的 g++4.8.1。我附上了在线编译器的链接以显示问题。
  • @KerrekSB 这真的是保证吗?我认为任何插入或删除都会使迭代器无效(这意味着不能保证它们仍然有效,尽管它们可以是)
  • @Ali:有趣;我看不到任何明显的UB。您正在利用向量的存储是连续的这一事实......

标签: c++ gcc iterator clang llvm


【解决方案1】:

虽然它可以通过迭代器来完成,但一个安全的选择是避免它们:

size_t size = v.size();  // Of course we shouldn't access .size() in the loop...
v.reserve(size * 2);     // Preallocation. Thanks @Ali for this performance hint
for (size_t i = 0; i < size; ++i)
    v.push_back(v[i]);

一般来说,使用迭代器同时修改数据结构(不仅仅是它的元素)是危险的;您应该仔细阅读迭代器何时失效以及何时可以安全地在修改后重用旧迭代器。因此,有时使用“旧”方法迭代随机访问序列是有意义的:使用索引变量。

【讨论】:

  • 刚刚在那里检查并回答了;谢谢你的提示,如果你知道有多少插入肯定会跟随,那么强制预​​分配确实是一个好主意。
  • 你如何看待迭代器?
【解决方案2】:

正如其他人已经指出的那样,您需要通过调用vector::reserve() 来确保向量在插入期间不会被重新分配。 (如果您选择将带有push_back() 的元素放入向量中,调用reserve 也是一个好主意。)

然后仍然存在迭代器失效问题(详见vector::insert()),但据我所知,下面的代码绕过了:

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

using namespace std;

int main() {

  vector<string> s{"abc", "def"};

  auto size = s.size();

  s.reserve(2*size); // <-- This one is essential to ensure
                     //     no reallocation during insertion

  const string* first = s.data(); // using pointer, instead of iterator

  copy(first, first+size, back_inserter(s));

  for (const auto& e : s)
    cout << e << '\t';

  cout << endl;
}

【讨论】:

  • 来自 cppreference.com:“过去的迭代器也无效。” s.end() 或s.begin() + s.size() 并没有什么区别;两者都是“过去的迭代器”。唯一的绕过方法是将第一个或最后一个元素区别对待,同时引入空向量的特殊情况。在我看来,这不值得付出努力,因为它的可读性不如使用普通的旧索引访问。
  • @leemes 感谢您的检查。但是,请注意s.data() 产生一个指针,而不是迭代器。所以s.data() + s.size() 不等于s.end()
  • 哦,我读你的代码太快了。在这种情况下,它可能确实有效。
  • @leemes 是的,这也经常发生在我身上。 :( 更新了代码,明确了类型并添加了注释。希望下一个快速阅读者不会错过这一点。
【解决方案3】:

如前所述,您似乎只需致电reserve

sv.reserve(sv.size() * 2);

这是因为如果发生重新分配,迭代器就会失效。你可以自己检查一下:

std::cout << sv.capacity() << "\n"; // 2

新大小将大于容量,因此会发生重新分配。

【讨论】:

  • 据我了解,最终迭代器可能仍然无效。
  • @Kit 也许这仅适用于push_back。但我不是专家。
  • 不幸的是,任何插入都使结束迭代器无效,即使在最后,即使没有重新分配。那是因为它在插入点之后。因此,尽管我认为它不应该定义行为没有任何严重的原因,但使用结束迭代器定义插入的范围不是(AFAIK)定义的行为。
  • @SteveJessop 请检查我的回答。这会调用 UB 吗?
  • @Ali:不确定。根据标准,指针first+size 无效,但您认为这只是意味着它不能再用于访问元素(以前不是这样,因为它是一个非终端指针)。当然在实践中,从vector 的 POV 中“无效”不能神奇地使指针不再与其他指针相等可比:-)
【解决方案4】:

这个方法也可以

vector<string> data = {"abc", "def"}; // Input String Vector
int len = data.size();
for(int i = 0; i < len; i++)
{
    data.push_back(data[i]); // Making Copy of String Vector
}

vector<string> data = {"abc", "def", "abc", "def"};
//Resultant String Vector

【讨论】:

    猜你喜欢
    • 2014-08-04
    • 2013-01-25
    • 2019-12-22
    • 2014-05-11
    • 1970-01-01
    • 2011-01-13
    • 1970-01-01
    • 2013-05-12
    相关资源
    最近更新 更多