【问题标题】:Modifying priority_queue of unique_ptrs修改 unique_ptrs 的 priority_queue
【发布时间】:2014-01-03 16:27:24
【问题描述】:

我正在尝试用 C++ 编写事件驱动的模拟。现在它只是一个unique_ptrs 到基本Event 类的基本优先级队列:

class Event
{
public:
    double time;
    Event(double time);
    virtual void handle() = 0;
};

struct EventCompare
{
    bool operator()(std::unique_ptr<Event> e1, std::unique_ptr<Event> e2) {
    return e1->time > e2->time;
    }
};

class DumpSimulationEvent : public Event
{
public:
    DumpSimulationEvent(const double time);
    void handle();
};

typedef std::priority_queue<std::unique_ptr<Event>, std::vector<std::unique_ptr<Event>>, EventCompare> EventQueue;

class Simulation
{
    double time;
    EventQueue eventQueue;
public:
    Simulation();
    void run();
};

Event::Event(const double t)
{
    time = t;
}

DumpSimulationEvent::DumpSimulationEvent(const double t) : Event(t)
{
}

void DumpSimulationEvent::handle()
{
    std::cout << "Event time: " << time;
}

Simulation::Simulation()
{
    time = 0;
    eventQueue = EventQueue();
    std::unique_ptr<DumpSimulationEvent> dumpEvent5(new DumpSimulationEvent(5));
  //eventQueue.emplace(dumpEvent5);
}

void Simulation::run()
{
    while (!eventQueue.empty()) {
        std::unique_ptr<Event> currentEvent = std::move(eventQueue.top());
      //eventQueue.pop();
        time += currentEvent->time;
        currentEvent->handle();
    }
}

Main 函数(上面未显示)只是创建一个 Simulation 实例并调用 run() 方法。问题是取消注释 emplace() 或 pop() 会导致

error C2280: 'std::unique_ptr<Event,std::default_delete<_Ty>>::unique_ptr(const std::unique_ptr<_Ty,std::default_delete<_Ty>> &)' : attempting to reference a deleted function  c:\program files (x86)\microsoft visual studio 12.0\vc\include\xutility 521 1

研究表明,最可能的原因是试图复制 unique_ptr。然而,我不知道它是否是真正的原因,它是否真的发生在注释行或只是在那里变得可见。将 std::move 添加到 emplace 参数似乎没有帮助。

【问题讨论】:

  • std::move(eventQueue.top()); 这将失败,因为priority_queue 仅提供对top 的常量访问。见stackoverflow.com/q/20149471/420683
  • 不是您看到的错误的原因,而是 Event 类应该有一个 virtual 析构函数,否则它是 UB 将 DumpSimulationEvent 对象粘贴到 unique_ptr&lt;Event&gt;
  • 定义虚拟析构函数是否意味着我必须显式定义析构函数并为派生类移动构造函数?

标签: c++ c++11 unique-ptr


【解决方案1】:

你的问题是你没有正确移动东西,但你试图在几个地方制作副本。

以下是使您的代码工作的差异,并带有一些评论:

 struct EventCompare
 {
-    bool operator()(std::unique_ptr<Event> e1, std::unique_ptr<Event> e2) {
+    bool operator()(std::unique_ptr<Event> const &e1, std::unique_ptr<Event> const &e2) {
     return e1->time > e2->time;
     }
 };

在这里,正如 juanchopanza 在他的回答中提到的那样,您必须通过引用而不是值来获取 std::unique_ptrs,否则您是在要求编译器为您制作副本,这是不允许的。

     time = 0;
     eventQueue = EventQueue();
     std::unique_ptr<DumpSimulationEvent> dumpEvent5(new DumpSimulationEvent(5));
-  //eventQueue.emplace(dumpEvent5);
+    eventQueue.emplace(std::move(dumpEvent5));
 }

在上面的代码中,您必须将您的std::unique_ptr 移动到队列中。 Emplace 不会神奇地移动东西,它只是将参数转发给构造函数。如果此处没有std::move,您要求制作一个副本。您也可以只写:eventQueue.emplace(new DumpSimulationEvent(5)); 并跳过中间对象。

     while (!eventQueue.empty()) {
-        std::unique_ptr<Event> currentEvent = std::move(eventQueue.top());
-      //eventQueue.pop();
+        std::unique_ptr<Event> currentEvent(std::move(const_cast<std::unique_ptr<Event>&>(eventQueu
+        eventQueue.pop();
         time += currentEvent->time;
         currentEvent->handle();

最后,在上面的代码中,您试图从eventQueue.top() 移动,但您不能从const 引用移动,这是top() 返回的内容。如果你想强制移动工作,你必须同时使用const_caststd::move(),如上所示。

这是完整的修改后的代码,可以在这里用g++-4.8 -std=c++11 编译:

#include <memory>
#include <queue>
#include <iostream>

class Event
{
public:
    double time;
    Event(double time);
    virtual void handle() = 0;
};

struct EventCompare
{
    bool operator()(std::unique_ptr<Event> const &e1, std::unique_ptr<Event> const &e2) {
    return e1->time > e2->time;
    }
};

class DumpSimulationEvent : public Event
{
public:
    DumpSimulationEvent(const double time);
    void handle();
};

typedef std::priority_queue<std::unique_ptr<Event>, std::vector<std::unique_ptr<Event>>, EventCompare> EventQueue;

class Simulation
{
    double time;
    EventQueue eventQueue;
public:
    Simulation();
    void run();
};

Event::Event(const double t)
{
    time = t;
}

DumpSimulationEvent::DumpSimulationEvent(const double t) : Event(t)
{
}

void DumpSimulationEvent::handle()
{
    std::cout << "Event time: " << time;
}

Simulation::Simulation()
{
    time = 0;
    eventQueue = EventQueue();
    std::unique_ptr<DumpSimulationEvent> dumpEvent5(new DumpSimulationEvent(5));
    eventQueue.emplace(std::move(dumpEvent5));
}

void Simulation::run()
{
    while (!eventQueue.empty()) {
        std::unique_ptr<Event> currentEvent(std::move(const_cast<std::unique_ptr<Event>&>(eventQueue.top())));
        eventQueue.pop();
        time += currentEvent->time;
        currentEvent->handle();
    }
}

【讨论】:

  • 谢谢,它成功了。有趣的是,它在没有 top() const_reference return 变通方法的情况下工作。我认为当有许多事件进入或退出队列时问题会出现?
  • @user3157796 你是说你在从const 引用移动时编译它吗?如果是这样,那可能是编译器或库错误,因为 const std::unique_ptr 被定义为不可移动。无论如何,保留const_cast 是(或应该)必要的,但您可以使用 operator= 而不是构造函数来进行移动。 (我在编辑代码时将其更改为构造函数,但这并不是绝对必要的)。
  • 是的,它只需将 std::move 添加到 emplace 并将比较器更改为根据 juanchopanza 的引用(他的答案由于某种原因消失了),同时保持 std::move(eventQueue.top( )) 原样。有问题的编译器是 MSVC 18.00.21005.1。 cplusplus.com/reference/queue/priority_queue/top 将 const_reference 显示为 C++11 的返回类型,而不是 const value_type&。也许它是一个可以处理移动的包装器?
  • @user3157796 好吧,即使它是一个包装器,如果它在转换时暴露一个非const std::unique_ptr &amp;&amp; 仍然是错误的。我怀疑 MSVC 的 std::unique_ptr 实现与标准相比过于宽松,这仍然是一个错误,只是目前还没有给您带来任何麻烦。为了便于携带,我会保留const_cast,但这取决于你! =)
  • A-a-一旦我将更多事件添加到队列中,我就会在比较器中遇到访问冲突。 :(
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-04-22
  • 2013-01-20
  • 2015-02-19
  • 1970-01-01
相关资源
最近更新 更多