我有一组字符串:
set <wstring> strings;
// ...
我希望根据谓词删除字符串,例如:
std::remove_if ( strings.begin(), strings.end(), []( const wstring &s ) -> bool { return s == L"matching"; });
当我尝试这个时,我得到以下编译器错误:
c:\Program Files (x86)\Microsoft Visual Studio 10.0\VC\include\algorithm(1840): error C2678: binary '=' : no operator found which takes a left-hand operand of type 'const std::basic_string<_Elem,_Traits,_Ax>'
该错误似乎表明std::string
没有按值复制构造函数(这将是非法的)。将std::remove_if
与std::set
一起使用有点不对吗?我应该做其他事情,例如set::find()
后面跟set::erase()
的几次迭代吗?
答案 0 :(得分:20)
std::remove_if
(或std::erase
)通过重新分配范围成员的值来工作。它不了解std::set
如何组织数据,或如何从其内部树数据结构中删除节点。实际上,仅使用对节点的引用是不可能的,而不需要set
对象本身。
标准算法旨在具有透明(或至少一致易于记忆)的计算复杂性。由于需要重新平衡树,因此选择性地从set
中删除元素的函数将是O(N log N),这不比调用my_set.remove()
的循环更好。所以,标准没有提供它,这就是你需要写的东西。
另一方面,从vector
一个一个地移除项目的天真手动编码循环将是O(N ^ 2),而std::remove_if
是O(N)。因此,图书馆确实在这种情况下提供了实实在在的好处。
一个典型的循环(C ++ 03风格):
for ( set_t::iterator i = my_set.begin(); i != my_set.end(); ) {
if ( condition ) {
my_set.erase( i ++ ); // strict C++03
// i = my_set.erase( i ); // more modern, typically accepted as C++03
} else {
++ i; // do not include ++ i inside for ( )
}
}
编辑(4年后!):i ++
在那里看起来很可疑。如果erase
在增量后运算符更新之前使i
无效,该怎么办?但这很好,因为它是一个重载operator++
而不是内置运算符。该函数可以就地安全地更新i
,然后会返回其原始值的副本。
答案 1 :(得分:9)
错误消息显示
没有找到哪个运算符采用了类型为“ const 的左手操作数” 的std :: basic_string的&LT; _Elem,_Traits,_AX&GT;'
注意const。编译器是正确的std::wstring
没有可以在const对象上调用的operator=
。
为什么字符串是const?答案是std::set
中的值是不可变的,因为集合中的值是有序的,更改值可能会更改集合中的顺序,从而使集合无效。
为什么编译器会尝试复制集合的值?
std::remove_if
(和std::remove
)实际上不会删除任何内容(也不能删除它们,因为它们没有容器,只有迭代器)。他们所做的是将不匹配标准的范围内的所有值复制到范围的开头,并将迭代器返回到匹配元素之后的下一个元素。然后,您应该从返回的迭代器手动擦除到范围的末尾。由于集合保持其元素的顺序,因此移动任何元素是错误的,因此remove_if
不能在集合(或任何其他关联容器)上使用。
简而言之,您必须使用std::find_if
和set::erase
的循环,如下所示:
template<class V, class P>
void erase_if(std::set<V>& s, P p)
{
std::set<V>::iterator e = s.begin();
for (;;)
{
e = std::find_if(e, s.end(), p);
if (e == s.end())
break;
e = s.erase(e);
}
}