Question

I am trying to write an event-driven simulation in C++. Right now it's just a bare-bones priority queue of unique_ptrs to base Event class:

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 function (not shown above) just creates an instance of Simulation and calls the run() method. Problem is that uncommenting either emplace() or pop() results in

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

Research indicates that most likely cause is an attempt to copy an unique_ptr. I am, however, at loss whether is it actual reason and does it actually happen at commented lines or just becomes visible there. Adding std::move to emplace argument doesn't seem to help.

Was it helpful?

Solution

Your problem is that you are not moving things correctly, but you are trying to make copies in several places.

Here is a diff that makes your code work, with some commentary:

 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;
     }
 };

Here, as juanchopanza mentioned in his answer, you have to take std::unique_ptrs by reference, not by value, otherwise you are asking the compiler to make copies for you, which is not allowed.

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

In the above code, you have to MOVE your std::unique_ptr into the queue. Emplace doesn't magically move things, it just forwards arguments to the constructor. Without std::move here, you are asking to make a copy. You could have also just written: eventQueue.emplace(new DumpSimulationEvent(5)); and skipped the intermediate object.

     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();

Finally, in the above code, you are trying to move from eventQueue.top(), but you can't move from a const reference, which is what top() returns. If you want to force the move to work, you have to use both const_cast and std::move() as shown above.

Here is the complete modified code which compiles fine here with 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();
    }
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top