Вектор общих указателей на шаблонные классы

У меня есть шаблонный класс TaskRunner, который принимает полиморфный тип Task, и я хочу создать для них контейнер общих указателей.

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

Однако я получаю error: no matching member function for call to 'push_back' с:

std::vector<Runner> runners;

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

Из-за:

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

TaskRunner<LoudTask> и TaskRunner<Task> — несвязанные классы, имеющие не больше общего, чем, скажем, int и std::string. Вы можете получить TaskRunner<T> из полиморфного базового класса, не являющегося шаблоном, например. TaskRunnerBase и хранить указатели на него.
Igor Tandetnik 10.04.2022 18:14
TaskRunner<QuietTask> не является подтипом TaskRunner<Task>, даже если QuietTask является подтипом Task.
chi 10.04.2022 18:16

@IgorTandetnik Я все еще получаю no known conversion from 'shared_ptr<TaskRunner<LoudTask>>' to 'const shared_ptr<TaskRunnerBase>'?

Chris Seymour 10.04.2022 18:33

Должен ли TaskRunner быть шаблоном? Кажется, что это побеждает цель Task быть полиморфным?

Alan Birtles 10.04.2022 18:55
Получение данных из формы с помощью JavaScript - краткое руководство
Получение данных из формы с помощью JavaScript - краткое руководство
Получить данные из формы с помощью JS очень просто: вы запрашиваете элемент формы, передаете его конструктору new FormData() и, наконец, получаете...
Пользовательские правила валидации в Laravel
Пользовательские правила валидации в Laravel
Если вы хотите создать свое собственное правило валидации, Laravel предоставляет возможность сделать это. Создайте правило с помощью следующей...
3 метода стилизации элементов HTML
3 метода стилизации элементов HTML
Когда дело доходит до применения какого-либо стиля к нашему HTML, существует три подхода: встроенный, внутренний и внешний. Предпочтительным обычно...
Формы c голосовым вводом в React с помощью Speechly
Формы c голосовым вводом в React с помощью Speechly
Пытались ли вы когда-нибудь заполнить веб-форму в области электронной коммерции, которая требует много кликов и выбора? Вас попросят заполнить дату,...
Стилизация и валидация html-формы без использования JavaScript (только HTML/CSS)
Стилизация и валидация html-формы без использования JavaScript (только HTML/CSS)
Будучи разработчиком веб-приложений, легко впасть в заблуждение, считая, что приложение без JavaScript не имеет права на жизнь. Нам становится удобно...
Flatpickr: простой модуль календаря для вашего приложения на React
Flatpickr: простой модуль календаря для вашего приложения на React
Если вы ищете пакет для быстрой интеграции календаря с выбором даты в ваше приложения, то библиотека Flatpickr отлично справится с этой задачей....
2
4
44
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Ответ принят как подходящий

Реализовал предложение Игоря Тандетника, и у меня это работает:

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

Выход:

running!
RUNNING!

Однако обратите внимание, что TaskRunner не обязательно должен быть шаблоном; как в настоящее время реализовано выше, у него есть своего рода двойная роль: (1) фабрика задач и (2) контейнер и средство выполнения задач.

ответ Паоло прекрасно отделяет это, здесь аспект фабрики перемещается в основную функцию.

Если предполагается, что TaskRunner запускает задачи, которые наследуются только от Task, вы можете рассмотреть возможность де-шаблона:

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

Это еще одна реализация, которая устраняет необходимость в другой иерархии наследования и виртуальной таблице, поскольку мы уже сделали это с помощью иерархии задач:

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

Другие вопросы по теме