how to move unique_ptr object between two STL containers

Viewed 442
#include <utility>
#include<unordered_map>
#include<queue>
using namespace std;


struct Task {
    char t;
    int cnt;
    Task(char ch, int cnt):t(ch), cnt(cnt){};
};
struct CompTask {
    bool operator() (const unique_ptr<Task>& a, const unique_ptr<Task>& b) {
        return a->cnt < b->cnt;
    }
};
class Schedule {
public:
    int schedule() {
        unordered_map<unique_ptr<Task>, int> sleep_q;
        priority_queue<unique_ptr<Task>, vector<unique_ptr<Task>>, CompTask> ready_q; // max heap
        ready_q.push(std::make_unique<Task>('A', 1));

        auto& ptr = ready_q.top();
        //sleep_q.insert({ptr, 1}); // compile error 
        sleep_q.insert({std::move(ptr), 1}); // compile error 
        // some other code...
        
        return 1;
    }
};
int main() {
    return 0;

}
// error:
cpp:38:17: error: no matching member function for call to 'insert'
        sleep_q.insert({std::move(ptr), 1}); // compile error 
        ~~~~~~~~^~~~~~

Programming context: I had a task class and the program attempts to simulate the task scheduling (which involves moving a task back and forth between a ready queue and a sleep queue). I have two std containers for ready queue and sleep queue respectively, the priority_queue has value type unique_ptr<Task>, the other is unorder_map (sleep queue) whose key is also unique_ptr<Task>. I had trouble moving the unique_ptr object from priorty_queue to unordered_map (shown in the code).

My questions are: (1) how to insert an item into the unordered_map, I had compilation errors on doing that. (2) in the problem context, which type of "pointer" would be preferred? unique_ptr<Task>, shared_ptr<Task>, or just Task*

2 Answers

There seems to be lack of move functionality out of std::priority_queue<>. You can go around it using const_cast, though in rare cases it might cause undefined behavior (when stored element type is const).

    int schedule() {
        unordered_map<unique_ptr<Task>, int> sleep_q;
        priority_queue<unique_ptr<Task>, vector<unique_ptr<Task>>, CompTask> ready_q; // max heap                                                                                                     
        ready_q.push(std::make_unique<Task>('A', 1));

        unique_ptr<Task> ptr = 
                std::move(const_cast<unique_ptr<Task>&>(ready_q.top()));
        //                ^ Here. priority_queue::top() returns `const&`.
        ready_q.pop(); // remove moved element from priority_queue
        sleep_q.insert(std::make_pair(std::move(ptr), 1));                                                                                                                          
        // some other code...                                                                                                                                                                         

        return 1;
    }

Reference: This answer

To move a unique_ptr between std containers requires destroying one unique_ptr (stored within the first std container) and passing the backing data to a new unique_ptr. This can be done with std::move. However, it's usually easier to use a shared_ptr. Which avoids the problem by allowing shared_ptr point to the same task to be in two std containers simultaneously. Even if just for a moment.

However, since you could have multiple shared_ptr objects pointing the same task the shared_ptr wouldn't work to uniquely identify the task. To do that, I would recommend creating a Task Id for each task. A simple integer would work for this, as long as you guarantee it's unique to that task. An integer task Id would work well with a map since the Id could be used as the key.

Related