在the other topic我试图解决this问题。问题是从std::string
中删除重复的字符。
std::string s= "saaangeetha";
由于订单不重要,所以我先排序s
,然后使用std::unique
,最后调整大小以获得the desired result:
aeghnst
这是对的!
现在我想做同样的事,但同时我希望字符的顺序完好无损。意思是,我想要这个输出:
sangeth
所以我写了this:
template<typename T>
struct is_repeated
{
std::set<T> unique;
bool operator()(T c) { return !unique.insert(c).second; }
};
int main() {
std::string s= "saaangeetha";
s.erase(std::remove_if(s.begin(), s.end(), is_repeated<char>()), s.end());
std::cout << s ;
}
这给出了这个输出:
saangeth
即,重复a
,但其他重复消失了。代码有什么问题?
无论如何我change my code了一下:(见评论)
template<typename T>
struct is_repeated
{
std::set<T> & unique; //made reference!
is_repeated(std::set<T> &s) : unique(s) {} //added line!
bool operator()(T c) { return !unique.insert(c).second; }
};
int main() {
std::string s= "saaangeetha";
std::set<char> set; //added line!
s.erase(std::remove_if(s.begin(),s.end(),is_repeated<char>(set)),s.end());
std::cout << s ;
}
输出:
sangeth
问题消失了!
第一个解决方案出了什么问题?
另外,如果我不创建成员变量unique
引用类型,那么the problem doesn't go。
std::set
或is_repeated
仿函数有什么问题?问题究竟在哪里?
我还注意到,如果is_repeated
仿函数被复制到某处,那么它的每个成员也会被复制。我没有在这里看到问题!
答案 0 :(得分:17)
Functors应该以一种仿函数的副本与原始仿函数相同的方式设计。也就是说,如果您复制一个仿函数然后执行一系列操作,无论您使用哪个仿函数,或者即使您将两个仿函数交错,结果也应该相同。这使STL实现可以灵活地复制仿函数并在它认为合适时传递它们。
使用您的第一个仿函数,此声明不成立,因为如果我复制您的仿函数然后调用它,您对其存储集所做的更改不会反映在原始仿函数中,因此副本和原始文件的执行方式会有所不同。类似地,如果您使用第二个仿函数并使其不通过引用存储其集合,则仿函数的两个副本将不会表现相同。
你的仿函数的最终版本起作用的原因是因为该集合是通过引用存储的,这意味着任何数量的tue仿函数副本的行为都会相同。
希望这有帮助!
答案 1 :(得分:15)
In GCC (libstdc++),remove_if
基本上实现为
template<typename It, typename Pred>
It remove_if(It first, It last, Pred predicate) {
first = std::find_if(first, last, predicate);
// ^^^^^^^^^
if (first == last)
return first;
else {
It result = first;
++ result;
for (; first != last; ++ first) {
if (!predicate(*first)) {
// ^^^^^^^^^
*result = std::move(*first);
++ result;
}
}
}
}
请注意,您的谓词将 by-value 传递给find_if
,因此结构以及因此在find_if
内修改的集合不会传播回调用方。
由于第一个副本出现在:
saaangeetha
// ^
"sa"
来电后,我会保留最初的find_if
。同时,predicate
的集合为空(find_if
内的插入是本地的)。因此,之后的循环将保持第3 a
。
sa | angeth
// ^^ ^^^^^^
// || kept by the loop in remove_if
// ||
// kept by find_if
答案 2 :(得分:5)
不是一个真正的答案,但作为另一个有趣的小问题,这确实有效,即使它使用原始的仿函数:
#include <set>
#include <iostream>
#include <string>
#include <algorithm>
#include <iterator>
template<typename T>
struct is_repeated {
std::set<T> unique;
bool operator()(T c) { return !unique.insert(c).second; }
};
int main() {
std::string s= "saaangeetha";
std::remove_copy_if(s.begin(), s.end(),
std::ostream_iterator<char>(std::cout),
is_repeated<char>());
return 0;
}
编辑:我认为它不会影响这种行为,但我也纠正了你的仿函数中的一个小滑动(operator()显然应该采用类型为T的参数,而不是char
)。
答案 3 :(得分:4)
我认为问题可能在于is_repeated
仿函数被复制到std::remove_if
的实现中。如果是这种情况,则使用默认的复制构造函数,然后调用std::set
复制构造函数。您最终会得到两个可能独立使用的is_repeated
个仿函数。但是,由于它们中的集合都是不同的对象,因此它们看不到相互的变化。如果您将字段is_repeated::unique
转换为引用,则复制的仿函数仍然使用原始集合,这是您在这种情况下所需的集合。
答案 4 :(得分:2)
Functor类应该是纯函数,并且没有自己的状态。有关此问题的详细解释,请参阅Scott Meyer的 Effective STL 一书中的第39项。但它的要点是你的仿函数类可以在算法中复制一次或多次。
答案 5 :(得分:1)
其他答案是正确的,因为问题在于您使用的仿函数不是可复制的安全。特别是,gcc(4.2)附带的STL将std::remove_if
实现为std::find_if
的组合,以找到要删除的第一个元素,然后是std::remove_copy_if
来完成操作。
template <typename ForwardIterator, typename Predicate>
std::remove_if( ForwardIterator first, ForwardIterator end, Predicate pred ) {
first = std::find_if( first, end, pred ); // [1]
ForwardIterator i = it;
return first == last? first
: std::remove_copy_if( ++i, end, fist, pred ); // [2]
}
[1]中的副本意味着找到的第一个元素被添加到仿函数的副本中,这意味着第一个'a'将在遗忘中丢失。仿函数也在[2]中被复制,如果不是因为该副本的原件是空的仿函数,那就没问题。
答案 6 :(得分:1)
根据remove_if
的实现,可以制作谓词的副本。要么重构你的仿函数并使其成为无状态,要么使用Boost.Ref来“传递对函数模板(算法)的引用,这些函数通常会复制它们的参数”,如下所示:
#include <set>
#include <iostream>
#include <string>
#include <algorithm>
#include <iterator>
#include <boost/ref.hpp>
#include <boost/bind.hpp>
template<typename T>
struct is_repeated {
std::set<T> unique;
bool operator()(T c) { return !unique.insert(c).second; }
};
int main() {
std::string s= "saaangeetha";
s.erase(std::remove_if(s.begin(), s.end(), boost::bind<bool>(boost::ref(is_repeated<char>()),_1)), s.end());
std::cout << s;
return 0;
}