【问题标题】:C++ initialize priority_queue with custom compare functionC ++使用自定义比较函数初始化priority_queue
【发布时间】:2016-03-05 01:42:53
【问题描述】:

我有一个 Dijkstra 类,它使用带有自定义比较功能的 priority_queue。我用using 语句将队列命名为DijkstraPriorityQueue。在类构造函数中,我初始化了队列。为此,我在 lambda 表达式中给出了比较函数。

对于第一个队列PQ1,比较函数是{ return distTo[u] > distTo[v]; },并且编译正常,因为vector<float> distTo 是该类的成员。

但是对于第二个队列PQ2,函数是{ return distTo2[u] > distTo2[v]; },其中vector<float> distTo2 只是构造函数中的一个临时变量,并且无法编译。 (我认为至少是这个原因)

另外,我随机尝试通过直觉将vector<float> distTo2 更改为static vector<float> distTo2 并编译,但我不认为这是我想要做的。我不熟悉函数内部的静态变量,因为 Java 或 C# 中不存在静态变量。无论如何,让下面的代码按预期编译和工作的干净解决方案是什么?

Dijkstra.h

class Dijkstra
{
public:
    Dijkstra();  
    ~Dijkstra();       

private:    
    vector<float> distTo;
};

Dijkstra.cpp

using DijkstraPriorityQueue = priority_queue<int, vector<int>, function<bool(int, int)>>;

Dijkstra::Dijkstra()
{           
    distTo = vector<float>(V, FLT_MAX);
    // Compiles fine
    DijkstraPriorityQueue PQ1 = DijkstraPriorityQueue([this](int u, int v)
        { return distTo[u] > distTo[v]; });

    vector<float> distTo2 = vector<float>(V, FLT_MAX);
    // Doesn't compile
    DijkstraPriorityQueue PQ2 = DijkstraPriorityQueue([this](int u, int v)
        { return distTo2[u] > distTo2[v]; });
}

编辑:

以下代码也可以编译。任何线索为什么?有人能解释一下 lambda 表达式上的 capture 是什么吗?或者在这种特定情况下我应该如何正确编写我的代码?

DijkstraPriorityQueue PQ2 = DijkstraPriorityQueue([distTo2](int u, int v)
    { return distTo2[u] > distTo2[v]; });

【问题讨论】:

  • 你试过here提到的解决方案吗?
  • @AnmolSinghJaggi 它与[=] 和[&amp;] 一起编译。任何线索我应该在这里使用什么?
  • 你应该使用[&amp;],因为它可以防止不必要的向量复制。
  • @AnmolSinghJaggi [=] 会复制整个向量吗?神圣的上帝之母。
  • 阅读this。

标签: c++ priority-queue


【解决方案1】:

您的问题主要有两个方面:

  • 这个“捕获”的东西是什么,为什么会出错?

  • 如何为优先队列指定自定义比较函数?

这些方面单独讨论最清楚。

不幸的是,所提供的(不完整的)示例代码不太适合讨论任何一个方面,所以我忽略了它。

什么是 lambda 捕获。

考虑以下代码:

#include <stdio.h>

struct S
{
    int a_;

    void foo() const
    {
        // Compiles nicely:
        [this]() -> void { printf( "%d\n", a_ ); }();

        // Doesn't compile, oh why!:
        int b = 666;
        [this]() -> void { printf( "%d\n", b ); }();
    }
};

auto main()
    -> int
{ S{ 42 }.foo(); }

MinGW g++ 5.1.0 提供以下诊断(编译错误):

x1.cpp:在 lambda 函数中: x1.cpp:14:44:错误:未捕获“b” [this]() -> void { printf("%d\n", b); }(); ^ x1.cpp:14:14:注意:lambda 没有默认捕获 [this]() -> void { printf("%d\n", b); }(); ^ x1.cpp:13:13:注意:此处声明的“int b” 诠释 b = 666; ^

为了理解“未捕获”,让我们手动实现 lambdas,只需进行与编译器相同的代码转换:

    void foo() const
    {
        // Compiles nicely:
        //[this]() -> void { printf( "%d\n", a_ ); }();
        class Functor_a
        {
        private:
            S const* captured_this_;

        public:
            void operator()()
            { printf( "%d\n", captured_this_->a_ ); }

            Functor_a( S const* this_capture )
                : captured_this_( this_capture )
            {}
        };
        Functor_a f_a{ this };
        f_a();

        // Doesn't compile, oh why!:
        int b = 666;
        // [this]() -> void { printf( "%d\n", b ); }();
        class Functor_b
        {
        private:
            S const* captured_this_;

        public:
            void operator()()
            { printf( "%d\n", b ); }

            Functor_b( S const* this_capture )
                : captured_this_( this_capture )
            {}
        };
        Functor_b f_b{ this };
        f_b();
    }
};

诊断现在更清楚了。由于Functor_b 是一个类,并且由于C++ 中的一个类是完全独立的实体,因此它的代码与foo() 的特定调用中的事物没有任何关系或访问权。因此编译器不接受对某些未指定的b 的引用,但请注意,如果您真的指的是包含范围内的b,那么嘿,那个名称b 在每次调用@ 时指的是不同的变量987654328@,这不是一个有效的选择:

x2.cpp:在成员函数'void S::foo() const::Functor_b::operator()()'中: x2.cpp:37:35:错误:使用局部变量和包含函数的自动存储 { printf("%d\n", b); } ^ x2.cpp:28:17:注意:此处声明的“int b” 诠释 b = 666; ^

一种解决方案是捕获 值,即将其复制到仿函数类实例中,例如如下:

        class Functor_b
        {
        private:
            int const captured_b_;

        public:
            void operator()()
            { printf( "%d\n", captured_b_ ); }

            Functor_b( int const b_capture )
                : captured_b_( b_capture )
            {}
        };
        Functor_b f_b{ b };     // ← The capture.
        f_b();                  // ← Using the captured value.

或者,您可以捕获指向变量的指针,按引用捕获。在这种情况下,指针仅在变量的生命周期内有效。所以你最好不要在那之后保留一个仿函数实例。

用 lambda 表示法表示,值的捕获可能如下所示:

[b]() -> void { printf( "%d\n", b ); }();

或者像这样,用一个通用的捕获-whatever's-needed-by-value =:

[=]() -> void { printf( "%d\n", b ); }();

通过引用捕获,即指针,如下所示:

[&]() -> void { printf( "%d\n", b ); }();

如何为std::priority_queue 指定比较函数。

例如像这样:

#include <iostream>
#include <string>
#include <queue>
#include <vector>
using namespace std;

struct S
{
    string name;
    int birth_year;
};

auto main() -> int
{
    struct Age_sort
    {
        auto operator()( S const& a, S const& b )
            -> bool
        { return (a.birth_year < b.birth_year); }
    };
    using Q = priority_queue< S, vector<S>, Age_sort >;
    Q pq;
    pq.push( S{ "beta", 1980 } );
    pq.push( S{ "alfa", 1992 } );
    pq.push( S{ "charlie", 1971 } );
    while( not pq.empty() )
    {
        cout << pq.top().name << ' ' << pq.top().birth_year << endl;
        pq.pop();
    }
}

【讨论】:

  • 感谢您非常详细的回答,我现在可以看到 capture 的真正作用了。
猜你喜欢
  • 2013-09-14
  • 1970-01-01
  • 2013-04-13
  • 1970-01-01
  • 1970-01-01
  • 2017-09-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多