C ++ 11 std :: set lambda比较函数

我想用自定义比较函数创build一个std::set 。 我可以将它定义为一个带有operator()的类,但我希望能够定义一个lambda函数,因此我决定在类的构造函数的初始化列表中定义lambda函数,该类的std::set成员。 但是我不能得到lambda的types。 在我继续之前,这里是一个例子:

 class Foo { private: std::set<int, /*???*/> numbers; public: Foo () : numbers ([](int x, int y) { return x < y; }) { } }; 

我search后发现两个解决scheme:一个,使用std::function 。 只要设置比较函数types为std::function<bool (int, int)>并像我一样传递lambda。 第二个解决scheme是写一个make_set函数,就像std::make_pair

解决scheme1:

 class Foo { private: std::set<int, std::function<bool (int, int)> numbers; public: Foo () : numbers ([](int x, int y) { return x < y; }) { } }; 

解决scheme2:

 template <class Key, class Compare> std::set<Key, Compare> make_set (Compare compare) { return std::set<Key, Compare> (compare); } 

问题是,我有充分的理由select一种解决scheme吗? 我更喜欢第一个,因为它使用标准function(make_set不是一个标准function),但我想知道:是否使用std::function使代码(可能)更慢? 我的意思是,它是否降低了编译器内联比较函数的机会,或者它应该足够聪明,就像它是一个lambda函数types,而不是std::function (我知道,在这种情况下,它可以不是lambdatypes,但你知道,我一般问)?

(我使用GCC,但是我想知道一般的编译器是做什么的)

总结,在我获得了大量的答案之后:

如果速度很关键,最好的解决scheme是使用operator()函数来使用类。 编译器最容易优化和避免任何间接。

为了便于维护和更好的通用解决scheme,使用C ++ 11function,使用std::function 。 它仍然很快(只比函数慢一点,但可能忽略不计),你可以使用任何函数 – std::function ,lambda,任何可调用的对象。

还有一个使用函数指针的选项,但是如果没有速度问题,我认为std::function更好(如果使用C ++ 11)。

有一个选项可以在其他地方定义lambda函数,但是你不能从比较函数获得lambdaexpression式,因为你可以使用operator()创build一个类,定义的位置不会是set结构无论如何。

还有更多的想法,比如使用授权。 如果你想更全面的解释所有的解决scheme,请阅读答案:)

是的,一个std::function几乎不可避免地引入了你的set 。 虽然编译器在理论上总是能够发现所有使用你的std::function调用lambdaexpression式,这个lambdaexpression式总是完全相同的lambdaexpression式,而这个lambdaexpression式既硬又脆。

脆弱的,因为在编译器之前,可以certificate所有对std::function的调用实际上是调用你的lambda,它必须certificate没有访问你的std::setstd::function为除lambda外的任何东西。 这意味着它必须跟踪所有可能的路线,以达到所有编译单元中的std::set ,并且certificate它们都不是。

在某些情况下这可能是可能的,但即使您的编译器设法certificate它,相对无害的更改也可能会破坏它。

另一方面,具有无状态operator()的函子很容易certificate行为,涉及到的优化是日常的事情。

所以是的,在实践中,我怀疑std::function可能会变慢。 另一方面, std::function解决scheme比make_set更容易维护,交换程序员的时间是非常容易的。

make_set有一个严重的缺点,就是必须从make_set调用中推断出任何这样的settypes。 通常是一个set存储持久化状态,而不是你在堆栈上创build的东西,然后让它脱离范围。

如果创build了静态或全局无状态的lambda auto MyComp = [](A const&, A const&)->bool { ... } ,则可以使用std::set<A, decltype(MyComp)>语法创buildset ,可以坚持,但编译器很容易优化(因为decltype(MyComp)所有实例是无状态函数)和内联。 我指出这一点,因为你把这个set粘在一个struct 。 (或者你的编译器支持

 struct Foo { auto mySet = make_set<int>([](int l, int r){ return l<r; }); }; 

我会觉得奇怪!)

最后,如果你担心性能,考虑std::unordered_set要快得多(代价是无法遍历顺序的内容,并且不得不写入/find一个好的散列值),而且std::unordered_set std::vector如果您有两阶段“插入所有内容”,然后“重复查询内容”, std::vector更好。 只需将其首先填充到vector ,然后sort unique erase进行sort ,然后使用免费的equal_rangealgorithm。

编译器不可能内联一个std :: function调用,而任何支持lambdas的编译器几乎肯定会内联functor版本,包括如果functor是一个未被std::function隐藏的lambda。

您可以使用decltype来获取lambda的比较器types:

 #include <set> #include <iostream> #include <iterator> #include <algorithm> int main() { auto comp = [](int x, int y){ return x < y; }; auto set = std::set<int,decltype(comp)>( comp ); set.insert(1); set.insert(10); set.insert(1); // Dupe! set.insert(2); std::copy( set.begin(), set.end(), std::ostream_iterator<int>(std::cout, "\n") ); } 

打印:

 1 2 10 

看它在Coliru上运行 。

一个无状态的lambda(即没有捕获的)可以衰减到一个函数指针,所以你的types可以是:

 std::set<int, bool (*)(int, int)> numbers; 

否则,我会去make_set解决scheme。 如果你不使用单行创build函数,因为它是非标准的,你不会写很多代码!

从我使用Profiler的经验来看,性能和美观之间的最佳折衷scheme是使用自定义委托实现,如:

https://codereview.stackexchange.com/questions/14730/impossibly-fast-delegate-in-c11

由于std::function通常有点太重了。 我不能评论你的具体情况,因为我不认识他们。

如果您决定将该set作为类成员,则在构造函数时初始化它的比较器,那么至less有一个间接级别是不可避免的。 考虑到只要编译器知道,你可以添加另一个构造函数:

  Foo () : numbers ([](int x, int y) { return x < y; }) { } Foo (char) : numbers ([](int x, int y) { return x > y; }) { } 

一旦你有一个types为Foo的对象,这个set的types并不包含哪个构造函数初始化它的比较器的信息,所以要调用正确的lambda需要一个间接的运行时select的lambda operator()

由于您使用的是捕获的lambdas,因此您可以使用函数指针typesbool (*)(int, int)作为比较器types,因为捕获的lambdas具有适当的转换函数。 这当然会涉及通过函数指针的间接寻址。

差异高度取决于您的编译器的优化。 如果它优化了std::function lambda,那么它们是等价的,如果不是,则在前者中引入一个间接指向后者的方法。