繁体   English   中英

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

[英]C++11 std::set lambda comparison function

我想用自定义比较功能创建一个std::set 我可以使用operator()将其定义为一个类,但是我想享受定义使用lambda的功能,因此我决定在具有std::set的类的构造函数的初始化列表中定义lambda函数。 std::set会员。 但是我无法获得lambda的类型。 在继续之前,这里有一个例子:

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

搜索后,我发现了两种解决方案:一种是使用std::function 只需将设置的比较函数类型设置为std::function<bool (int, int)>然后像我一样完全传递lambda即可。 第二种解决方案是编写一个make_set函数,如std::make_pair

解决方案1:

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

解决方案2:

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

问题是,我是否有充分的理由选择一个解决方案而不是另一个解决方案? 我喜欢第一个,因为它利用了标准功能(make_set不是标准功能),但是我想知道:使用std::function会使代码(可能)变慢吗? 我的意思是,这是否会降低编译器内联比较函数的机会,还是应该足够聪明,使其表现出完全相同的行为,就像它是lambda函数类型而不是std::function (我知道,在这种情况下,它可以不是lambda类型,但您知道,我是在问)。

(我使用GCC,但是我想知道流行的编译器通常会做什么)

总结,在我获得了很多答案之后:

如果速度至关重要,最好的解决方案是使用带有operator()仿函数的类。 编译器最容易优化和避免任何间接调用。

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

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

有一个选项可以在其他地方定义lambda函数,但随后您就无法从作为lambda表达式的比较函数中获得任何收益,因为您还可以使用operator()将其设为一个类,并且定义的位置不会成为set构造。无论如何。

还有更多想法,例如使用委托。 如果您想对所有解决方案进行更彻底的解释,请阅读答案:)

编译器不太可能能够内联std :: function调用,而任何支持lambda的编译器几乎都可以内联functor版本,包括该functor是不是被std::function隐藏的lambda。

您可以使用decltype获取lambda的比较器类型:

#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实时运行。

是的, std::function向您的set引入了几乎不可避免的间接访问。 从理论上讲,编译器始终可以弄清楚对setstd::function所有使用都涉及在始终完全相同的lambda的lambda上调用它,这既困难又极其脆弱。

易碎,因为在编译器可以证明对std::function所有调用实际上都是对lambda的调用之前,它必须证明对std::set任何访问都无法将std::function为除lambda之外的任何值。 这意味着它必须跟踪所有可能的路径以到达所有编译单元中的std::set ,并证明它们中没有一个这样做。

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

另一方面,具有无状态operator()的仿函数很容易证明行为,而涉及到的优化是日常工作。

所以是的,在实践中,我怀疑std::function可能会更慢。 另一方面, std::function解决方案比make_set维护起来更容易维护,并且交换程序员时间以提高程序性能是很容易实现的。

make_set具有严重的缺点,即必须从对make_set的调用中推断出任何此类set的类型。 通常, set存储持久状态,而不是您在堆栈上创建的持久状态,然后超出范围。

如果您创建了静态或全局无状态Lambda auto MyComp = [](A const&, A const&)->bool { ... } ,则可以使用std::set<A, decltype(MyComp)>语法创建一个set可以持久存在,但易于编译器优化(因为decltype(MyComp)所有实例都是无状态函子)和内联。 我指出这一点,因为您将set粘贴在struct (或者您的编译器是否支持

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

我会感到惊讶!)

最后,如果您担心性能,请考虑使用std::unordered_set更快(以无法按顺序遍历内容以及必须编写/查找良好的哈希为代价),以及经过排序的std::vector如果您有两阶段“全部插入”,然后“反复查询内容”,则std::vector更好。 只需将其首先填充到vector ,然后sort unique erase进行sort ,然后使用免费的equal_range算法即可。

无状态的lambda(即没有捕获的lambda)可以衰减为函数指针,因此您的类型可以是:

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

否则我会去make_set解决方案。 如果您不使用单行创建功能(因为它是非标准的),则不会编写太多代码!

根据我在探查器中玩耍的经验,性能和美观之间的最佳折衷方案是使用自定义委托实现,例如:

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

由于std::function通常太重了。 我无法评论您的具体情况,但我不知道。

如果确定要将该set作为类成员,并在构造函数时初始化其比较器,则不可避免要至少有一个间接级别。 考虑一下编译器所知道的,您可以添加另一个构造函数:

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

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

一旦有了类型为Foo的对象, set的类型就不会包含有关构造函数初始化其比较器的信息,因此要调用正确的lambda,需要对运行时选择的lambda operator()进行间接调用。

由于您使用的是不可捕获的lambda,因此可以使用函数指针类型bool (*)(int, int)作为比较器类型,因为不可捕获的lambda具有适当的转换功能。 当然,这将涉及通过函数指针进行的间接访问。

差异很大程度上取决于编译器的优化。 如果它在std::function优化了lambda,那么它们是等效的,否则,您将在前者中引入一种在后者中没有的间接寻址。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM