Vector of shared pointers to templated classes

Viewed 90

I have a templated class TaskRunner that takes a polymorphic type Task and I want to create a container of shared pointers to them.

class Task {
    virtual void run() = 0;
};

class LoudTask : Task {
    void run() {
        std::cout << "RUNNING!" << std::endl;
    }
};

class QuietTask : Task {
    void run() {
        std::cout << "running!" << std::endl;
    }
};

template<typename T> class TaskRunner {
    public:
        TaskRunner<T>() {
            task = std::make_unique<T>();
        }
    private:
        std::unique_ptr<T> task;
};


using Runner = std::shared_ptr<TaskRunner<Task>>;

However I get error: no matching member function for call to 'push_back' with:

std::vector<Runner> runners;

runners.push_back(std::make_shared<TaskRunner<QuietTask>>());
runners.push_back(std::make_shared<TaskRunner<LoudTask>>());

Due to:

note: candidate function not viable: no known conversion from 'shared_ptr<TaskRunner>' to 'const shared_ptr<TaskRunner>' for 1st argument

3 Answers

Implemented IgorTandetnik's suggestion, and it works for me:

#include <iostream>

#include <memory>
#include <vector>

class Task {
    virtual void run() = 0;
};

class LoudTask : Task {
public:
    void run() {
        std::cout << "RUNNING!" << std::endl;
    }
};

class QuietTask : Task {
public:
    void run() {
        std::cout << "running!" << std::endl;
    }
};

class TaskRunnerBase
{
public:
    virtual void run() =0;
};

template <class T>
class TaskRunner: public TaskRunnerBase {
    public:
        TaskRunner():
            task(std::make_unique<T>()) {
        }
        void run() override
        {
            task->run();
        }
    private:
        std::unique_ptr<T> task;
};

int main()
{
    using Runner = std::shared_ptr<TaskRunnerBase>;
    std::vector<Runner> runners;
    
    runners.push_back(std::make_shared<TaskRunner<QuietTask>>());
    runners.push_back(std::make_shared<TaskRunner<LoudTask>>());

    runners[0]->run();
    runners[1]->run();
}

Output:

running!
RUNNING!

Note however that TaskRunner doesn't need to be a template; as it is currently implemented above, it has a kind of double role: (1) task factory, and (2) container and runner of tasks.

paolo's answer separates this out nicely, there, the factory aspect is moved to the main function.

If TaskRunner is supposed to run tasks that inherit from Task only, you may consider de-templatizing it:

#include <iostream>
#include <memory>
#include <vector>

class Task {
   public:
    virtual void run() = 0;
};

class LoudTask : public Task {
   public:
    void run() override { std::cout << "RUNNING!" << std::endl; }
};

class QuietTask : public Task {
   public:
    void run() override { std::cout << "running!" << std::endl; }
};

class TaskRunner {
   public:
    explicit TaskRunner(std::unique_ptr<Task>&& task_)
        : task(std::move(task_)) {}

    void run() {
        if (this->task) this->task->run();
    }

   private:
    std::unique_ptr<Task> task;
};

int main() {
    using Runner = std::shared_ptr<TaskRunner>;
    std::vector<Runner> runners;

    runners.push_back(
        std::make_shared<TaskRunner>(std::make_unique<QuietTask>()));
    runners.push_back(
        std::make_shared<TaskRunner>(std::make_unique<LoudTask>()));

    for (auto& runner : runners) runner->run();
}

This is another implementation that eliminates the need for another inheritance hierarchy and vtable since we already accomplished that with the Task hierarchy:

#include <iostream>
#include <vector>

class Task {
public:
    virtual void run() = 0;
};

class LoudTask : public Task {
public:
    void run() {
        std::cout << "RUNNING!" << std::endl;
    }
};

class QuietTask : public Task {
public:
    void run() {
        std::cout << "running!" << std::endl;
    }
};

class TaskRunner {
public:
    TaskRunner(std::unique_ptr<LoudTask> task) : m_task{ std::unique_ptr<Task>(task.release()) } {}
    TaskRunner(std::unique_ptr<QuietTask> task) : m_task{ std::unique_ptr<Task>(task.release()) } {}
    void run()
    {
        m_task->run();
    }
private:
    std::unique_ptr<Task> m_task;
};

using Runner = std::shared_ptr<TaskRunner>;

int main()
{
    std::vector<Runner> runners;

    runners.push_back(std::make_shared<TaskRunner>(std::make_unique<QuietTask>()));
    runners.push_back(std::make_shared<TaskRunner>(std::make_unique<LoudTask>()));

    runners[0]->run();
    runners[1]->run();
}
Related