C ++,将副本设置为vector


146

我需要复制std::setstd::vector

std::set <double> input;
input.insert(5);
input.insert(6);

std::vector <double> output;
std::copy(input.begin(), input.end(), output.begin()); //Error: Vector iterator not dereferencable

问题出在哪里?


5
还有assign()功能:output.assign(input.begin(), input.end());
Gene Bushuyev 2011年

您的向量是空的。尽管人们在下面指出,但是有许多种补救方法。
AJG85 2011年

@Gene:assign()想提前保留()必要的存储量。除非迭代器严格是InputIterator,否则它将使用输入迭代器确定所需的数量,在这种情况下,它将跳过保留并在每个push_back()上重新分配。在频谱的另一端,BiderectionalIterators将允许它仅减去end-开始。但是,std :: set的迭代器都不是(它们是ForwardIterator),这很不幸:在这种情况下,assign()只会遍历整个集合来确定其大小-在大型集合上性能较差。
谢尔盖·舍甫琴科

Answers:


213

您需要使用back_inserter

std::copy(input.begin(), input.end(), std::back_inserter(output));

std::copy不会将元素添加到您要插入的容器中:它不能;它只有一个迭代器进入容器。因此,如果将输出迭代器直接传递给std::copy,则必须确保其指向的范围至少足以容纳输入范围。

std::back_inserter创建一个输出迭代器,该迭代器push_back为每个元素调用一个容器,以便将每个元素插入到容器中。另外,您可以在中创建足够数量的元素std::vector来保存要复制的范围:

std::vector<double> output(input.size());
std::copy(input.begin(), input.end(), output.begin());

或者,您可以使用std::vector范围构造函数:

std::vector<double> output(input.begin(), input.end()); 

3
詹姆斯,您好,而不是您的std :: copy行(答案中的第一个代码块),我能做output.insert(output.end(), input.begin(), input.end());吗?
user2015453

或者只是使用cbegin和cend版本:output.insert(output.cend(), input.cbegin(), input.cend());您如何看待?谢谢。
user2015453

2
我应该output.reserve(input.size()); 我自己还是可以希望某些编译器为我做这件事?
jimifiki 2014年

@jimifiki,我不希望害怕。
亚历克西斯·威尔克

您的第一个向量初始化不正确。您创建一个input,size()空条目数组,然后在其后追加追加。我认为您是要使用std::vector<double> output; output.reserve(input.size()); std::copy(...);
亚历克西斯·威尔克

121

只需对带有迭代器的向量使用构造函数:

std::set<T> s;

//...

std::vector v( s.begin(), s.end() );

假设您只想要v中s的内容,而在将数据复制到v中之前v中没有任何内容。


42

这是另一个使用方法vector::assign

theVector.assign(theSet.begin(), theSet.end());

24

您没有在向量对象中保留足够的空间来容纳集合的内容。

std::vector<double> output(input.size());
std::copy(input.begin(), input.end(), output.begin());

1
这不值得-1。特别是,这允许向量仅进行一次分配(因为它无法确定O(1)中设置的迭代器的距离),并且,如果没有定义向量,则构造时每个元素都将为零。值得让副本归结为memcpy。如果该实现指出可以删除vector的ctor中的循环,则后者仍然值得。当然,前者也可以保留。
Fred Nurk 2011年

我不知道。让我帮你。
wilhelmtell

我给了您-1,但这是我的想法。进行一些小的编辑,以便我可以撤消我的投票,我会给你+1:由于fail-first属性,这实际上是一个非常干净的解决方案。
弗雷德·富

我只是想知道,如果我自己编辑答案,我可以进行投票。这样做,为您提供了+1的故障优先内存分配。抱歉!
Fred Foo

3

我认为最有效的方法是预分配然后放置元素:

template <typename T>
std::vector<T> VectorFromSet(const std::set<T>& from)
{
    std::vector<T> to;
    to.reserve(from.size());

    for (auto const& value : from)
        to.emplace_back(value);

    return to;
}

这样,我们将只为每个元素调用复制构造函数,而不是首先调用默认构造函数,然后为上面列出的其他解决方案复制赋值运算符。下面有更多说明。

  1. back_inserter可以使用但是它将在向量(https://en.cppreference.com/w/cpp/iterator/back_insert_iterator)上调用push_back()。 emplace_back()效率更高,因为它避免了在使用push_back()时创建临时文件。平凡构造的类型不是问题,但对于非平凡构造的类型(例如std :: string),这将是一个性能暗示。

  2. 我们需要避免使用带有size参数的向量来构造矢量,这会导致默认构造所有元素(一无所获)。例如,与使用std :: copy()的解决方案类似。

  3. 最后,vector :: assign()方法或采用迭代器范围的构造方法也不是很好的选择,因为它们将在以下位置调用std :: distance()(以了解元素数)集合迭代器。这将对所有set元素造成不必要的额外迭代,因为set是Binary Search Tree数据结构,并且没有实现随机访问迭代器。

希望有帮助。


请添加对权威机构的引用,为什么这样做很快,以及为什么back_inserter不需要使用a
Tarick Welling

在答案中添加了更多说明。
dshvets19

1

std::copy不能用于插入空容器。为此,您需要像这样使用insert_iterator:

std::set<double> input;
input.insert(5);
input.insert(6);

std::vector<double> output;
std::copy(input.begin(), input.end(), inserter(output, output.begin())); 

3
向量第一次重新分配失败:output.begin()中的迭代器无效。
Fred Nurk 2011年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.