std :: transform需要特别小心

时间:2014-02-04 18:55:57

标签: c++ stl stl-algorithm

我不明白为什么这段代码会编译:

#include <set>
#include <list>
#include <algorithm>

int modify(int i)
{
 return 2*i;
}

int main (int args, char** argv)
{
  std::set<int> a;
  a.insert(1);
  a.insert(2);
  a.insert(3);

  std::list<int> b;  // change to set here

  std::transform(a.begin(), a.end(), b.begin(), modify);   // line 19
}

虽然,如果我只是将b的类型从std::list<int>更改为std::set<int>,它会在编译时(第19行)失败,并显示错误:只读变量不可分配< / em>的。 要将b用作集合,我需要将变换行更改为

std::transform(a.begin(), a.end(), std::inserter(b, b.begin()), modify);

为什么?我不知何故猜测原因与set是一个关联容器这一事实有关,而list是一个序列容器,但我可能完全不在这里。

修改

我忘了提到:我在gcc 3.4.2和llvm 3.3上使用默认标准(c ++ 98)尝试过这个。我使用c ++ 03在llvm 3.3上再次尝试,我得到了相同的行为。

4 个答案:

答案 0 :(得分:4)

在没有std::inserter的代码中,transform分配给*b.begin()。在set的情况下,它是对元素的const引用(自C ++ 11以来)。因此,编译时错误。

对于list,它仍然分配给*b.begin(),它编译但行为未定义,因为列表的大小为0.因此b.begin()可能无法解除引用。

这是正确的,这与set是一个关联容器这一事实有关,而list是一个序列。关联容器不允许您修改用作键的元素部分。如果set部分元素,则对于map,您可以修改值,但不能修改密钥。

std::inserter的重点是安排而不是通过迭代器分配,而是调用insert

答案 1 :(得分:3)

首先,您拥有的代码表现出未定义的行为,因为目标列表实际上没有空间。使用back_inserter可以随时创建空间。

对于集合,集合的元素是不可变的。这就是为什么你不能分配给一个解除引用的迭代器,即使你有空间。但使用inserter完全没问题。

答案 2 :(得分:1)

此记录

std::transform(a.begin(), a.end(), b.begin(), modify);

std::list<int>无效(尽管它可以在C ++ 2003中为std :: list或std :: set编译,其中set具有非const迭代器),因为你定义了一个空列表。

std::list<int> b;

当使用这样的记录时,假设输出容器已经具有

范围内的元素
[b.begin(), b.begin() + distance( a.begin(), a.end() ) )

因为他们被重新分配了。 因此,如果考虑一个集合,那么假设该集合已经具有所有必需元素,但您可能不会更改它们。当您使用iterator adapter std :: insert_iterator时,它会在容器中添加新元素。所以在这种情况下你可以使用一套。

答案 3 :(得分:1)

在C ++ 03中,此代码编译但导致未定义的行为 - 您不能简单地更改set的值,因为它们必须按升序排列。在已修复的c ++ 11中,set::iteratorconst T上的双向迭代器,因此您根本无法更改其值。 std::inserter不会更改现有值,而是会在operator++中插入新值,因此一切正常