【问题标题】:Can you swap a std::queue with a lambda comparator?你可以用 lambda 比较器交换 std::queue 吗?
【发布时间】:2014-01-12 02:13:40
【问题描述】:

我正在尝试使用https://stackoverflow.com/a/709161/837451 中的示例通过交换清除 std::queue。但是,由于“已删除函数”错误,它似乎不适用于 lambda 比较器。

最小的工作失败示例:

#include <queue>
#include <vector>
using namespace std;
int main(){
    typedef pair<int,float> ifpair;
    auto comp = []( ifpair a,  ifpair b ) { return a.second > b.second; };
    typedef priority_queue< ifpair , vector<ifpair>, decltype( comp ) > t_npq;
    t_npq npq( comp );
    //do something with npq. finish using it (without emptying it) and clear for next round
    t_npq empty( comp );
    swap(npq , empty);
}

编译

g++ -std=c++11 /tmp/test.cpp -o /tmp/o

我收到以下错误:

/usr/include/c++/4.8/bits/move.h:176:11: error: use of deleted function ‘main()::__lambda0& main()::__lambda0::operator=(const main()::__lambda0&)’
   __a = _GLIBCXX_MOVE(__b);
       ^
/tmp/test.cpp:6:18: note: a lambda closure type has a deleted copy assignment operator

g++ -v

Using built-in specs.
COLLECT_GCC=g++
COLLECT_LTO_WRAPPER=/usr/lib/gcc/x86_64-linux-gnu/4.8/lto-wrapper
Target: x86_64-linux-gnu
Configured with: ../src/configure -v --with-pkgversion='Ubuntu/Linaro 4.8.1-10ubuntu9' --with-bugurl=file:///usr/share/doc/gcc-4.8/README.Bugs --enable-languages=c,c++,java,go,d,fortran,objc,obj-c++ --prefix=/usr --program-suffix=-4.8 --enable-shared --enable-linker-build-id --libexecdir=/usr/lib --without-included-gettext --enable-threads=posix --with-gxx-include-dir=/usr/include/c++/4.8 --libdir=/usr/lib --enable-nls --with-sysroot=/ --enable-clocale=gnu --enable-libstdcxx-debug --enable-libstdcxx-time=yes --enable-gnu-unique-object --enable-plugin --with-system-zlib --disable-browser-plugin --enable-java-awt=gtk --enable-gtk-cairo --with-java-home=/usr/lib/jvm/java-1.5.0-gcj-4.8-amd64/jre --enable-java-home --with-jvm-root-dir=/usr/lib/jvm/java-1.5.0-gcj-4.8-amd64 --with-jvm-jar-dir=/usr/lib/jvm-exports/java-1.5.0-gcj-4.8-amd64 --with-arch-directory=amd64 --with-ecj-jar=/usr/share/java/eclipse-ecj.jar --enable-objc-gc --enable-multiarch --disable-werror --with-arch-32=i686 --with-abi=m64 --with-multilib-list=m32,m64,mx32 --with-tune=generic --enable-checking=release --build=x86_64-linux-gnu --host=x86_64-linux-gnu --target=x86_64-linux-gnu
Thread model: posix
gcc version 4.8.1 (Ubuntu/Linaro 4.8.1-10ubuntu9) 

我有点好奇这里到底发生了什么,但更重要的是我真的很想知道如何使这项工作发挥作用。

【问题讨论】:

  • 在我的机器上工作!您使用的是什么编译器(和版本)?标准库实现的同上。
  • 次优快速修复:使用std::function&lt;bool(ifpair,ifpair)&gt;
  • 你真的希望 comp 的值通过值而不是通过 ref 或 const ref 传递吗?
  • @woolstar:可能是的。这些是非常小的值,复制起来很便宜。
  • 除了我之前的评论:甚至bool(*)(ifpair,ifpair);这应该可以工作并且不会增加std::function的开销

标签: c++ c++11 stl lambda deleted-functions


【解决方案1】:

虽然 lambda 表达式的结果是可移动构造的,但它不一定是可移动赋值的,当然也不是可复制的。我会通过为比较器对象使用std::reference_wrapper&lt;decltype(comp)&gt; 来绕过这个问题:

typedef pair<int,float> ifpair;
auto comp = []( ifpair a,  ifpair b ) { return a.second > b.second; };
typedef priority_queue< ifpair , vector<ifpair>,
                        std::reference_wrapper<decltype( comp ) >> t_npq;
t_npq npq( std::ref(comp) );
t_npq empty( std::ref(comp) );
swap(npq , empty);

由于引用包装器保留了 lambda 表达式的完整类型信息,即使闭包不为空,这也应该可以工作,并且在可行的情况下,应该可以内联函数。

【讨论】:

  • 哇,与函数指针相比​​,我在实际代码中的速度提高了约 10%。这是-O2,所以我想这是内联。
  • @mmdanziger:我猜这归结为内联和避免间接。看看自定义函数对象的性能也会很有趣......
  • +1 我只是在想std::function。然而,虽然我的意思是 +1,但我认为如果它指出只有 lambda 可能 具有隐式声明的移动构造函数,答案会更完整。据我所知(在标准中)关于移动赋值运算符没有任何说法。 :-(
  • @Cheersandhth.-Alf:我已经扩展了答案。 usinf std::ref() 的方法没有任何这些操作。
【解决方案2】:

正如编译错误所示,lambda 对象不可赋值。您可以为队列使用不同类型的函子,但仍将其编写为 labmda:

  1. 使用std::function&lt;bool(ifpair,ifpair)&gt;http://ideone.com/HZywoV

    但是由于std::function 的实现中有更多的间接性,这增加了(可能值得注意的)开销,但我想这在很大程度上取决于标准库的实现和编译器优化。可能是关于代码外观的最佳解决方案。

  2. 使用函数指针bool(*)(ifpair,ifpair):http://ideone.com/ZhFq3C

    std::function 相比,与您当前的解决方案相比,这不应该受到任何开销的影响,因为可能会对您的 lambda 代码进行一些编译器优化,而这些优化是不可能的(即将其内联到 std::queue 的其余部分中)例如消除复制两对的代码)。不过,使用函数指针看起来很老派。

  3. 使用自定义仿函数类,它可以很简单:http://ideone.com/9pcQFc

    template<typename Pair>
    struct GreaterBySecond {
        bool operator()(Pair a, Pair b) const {
            return a.second > b.second;
        }
    };
    

    这应该消除上面讨论的所有开销。如果性能很重要,我更喜欢这个。

【讨论】:

    【解决方案3】:

    Lambda 不可分配 – 5.1.2/19:

    与 lambda 表达式关联的闭包类型具有已删除的默认构造函数和已删除的复制赋值运算符。

    容器的交换也想分配比较器,所以这不起作用。

    但是,您可以通过首先将无状态 lambda 转换为函数指针来轻松使其工作:

    bool (*p)(ifpair, ifpair) = [](ifpair a, ifpair b) { return a.second > b.second; };
    

    现在使用:

    priority_queue<ifpair, vector<ifpair>, bool(*)(ifpair, ifpair)>
    

    (您可能想为函数类型引入 typedef:using comp_type = bool(iftype, iftype),然后在任何地方使用 comp_type *。)

    【讨论】:

    • 通过 lambda 调用通常可以内联。通过指针调用一般不能内联。
    • @DietmarKühl:很有趣。如果这是一个问题,也许需要一个自写的函子。
    • @leemes:虽然我认为我会编写函数对象,但我认为使用std::reference_wrapper&lt;...&gt; 避免了编写函数的需要,同时保留了内联函数的能力(见我的回答:-)
    • @DietmarKühl:非常好。
    【解决方案4】:

    你试过用std::function吗?

    #include <queue>
    #include <vector>
    #include <functional>
    using namespace std;
    int main(){
        typedef pair<int,float> ifpair;
        std::function< bool ( ifpair, ifpair )> comp = []( ifpair a,  ifpair b ) { return a.second > b.second; };
        typedef priority_queue< ifpair , vector<ifpair>, decltype( comp ) > t_npq;
        t_npq npq( comp );
        //do something with npq. finish using it (without emptying it) and clear for next round
        t_npq empty( comp );
        swap(npq , empty);
    }
    

    【讨论】:

    • 对于一个非常便宜的问题,这是一个非常昂贵的解决方案。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-05-26
    • 2017-10-07
    • 2013-01-31
    • 2013-07-03
    相关资源
    最近更新 更多