Ошибка по адресу:
class X {
....
std::queue<TaskPtr<T> > m_queue; // <--- T is unknown
};
В этот момент компилятор хочет знать тип задачи, но вы хотите просто сохранить все задачи независимо от их типа. Чтобы выяснить, как заставить это работать, посмотрите на использование T
и посмотрите, как избавиться от него.
template <typename T>
class Task {
std::shared_ptr<T> getValue() {
return m_value;
}
void execute() {
m_value = std::make_shared<T>(m_func());
}
....
};
Если бы это было только execute
, тогда жизнь была бы простой, так как вызывающему execute()
не важно, что такое T
, только то, что операция выполнена. Если бы это было только так, то решение было бы тривиальным:
class TaskBase
{
public:
virtual ~TaskBase() = default;
TaskBase(const TaskBase &) = default; // and so on....
virtual void execute() = 0;
};
template <typename T>
class Task : public TaskBase {
....
};
Затем просто сохраните указатель на TaskBase
вместо Task<T>
.
Решение getValue()
немного сложнее. Вам необходимо использовать динамическое приведение из TaskBase к фактическому Task из getValue<T>()
:
template <typename T>
std::shared_ptr<T> Task<T>::getValue() {
return m_value;
}
template<typename T>
std::shared_ptr<T> TaskBase::getValue()
{
auto childThis = dynamic_cast<Task<T>*>(this);
if (childThis == nullptr) {
// or maybe throw an exception
return nullptr;
}
return childThis->getValue();
}
Использование более сложное, так как пользователь должен знать, какой тип хранится в задаче:
void foo(std::shared_ptr<TaskBase> ptr)
{
auto ifInt = ptr->getValue<int>();
auto ifDouble = ptr->getValue<double>();
... more code ..
}
В этом случае Task<int>
будет обнаружен ifInt
, но с Task<unsigned>
это не удастся, поскольку ifInt==nullptr
.
Очевидно, приведенное выше объяснение недостаточно ясно, поэтому вот полный исходный код, который компилируется и работает:
#include <memory>
#include <functional>
#include <queue>
#include <iostream>
class TaskBase
{
public:
virtual ~TaskBase() = default;
TaskBase() = default;
TaskBase(const TaskBase &) = default; // and so on....
virtual void execute() = 0;
template <typename T>
std::shared_ptr<T> getValue();
};
template <typename T>
class Task : public TaskBase {
public:
Task(const std::function<T()>& func)
: m_func(func)
{
// some stuff here
}
void execute() override {
m_value = std::make_shared<T>(m_func());
}
std::shared_ptr<T> getValue() {
return m_value;
}
private:
std::shared_ptr<T> m_value;
std::function<T()> m_func;
};
template <typename T>
std::shared_ptr<T> TaskBase::getValue()
{
auto downCast = dynamic_cast<Task<T>*>(this);
if (downCast)
return downCast->getValue();
else
return nullptr;
}
using TaskPtr = std::shared_ptr<TaskBase>;
class X {
// some boiler plate code
public:
void addTask(TaskPtr task) {
m_queue.push(task);
}
void loop() {
// do some stuff
auto item = m_queue.front();
item->execute();
m_queue.pop();
// continue looping
}
std::queue<TaskPtr> m_queue;
};
int main()
{
X x;
TaskPtr task = std::make_shared<Task<int>>(
[] { std::cout << "int task execution\n"; return 5;});
x.addTask(task);
x.loop();
std::cout << "getValue<int> --> ";
auto valPtr = task->getValue<int>();
if (valPtr)
std::cout << *valPtr << '\n';
else
std::cout << "nullptr\n";
std::cout << "getValue<float> --> ";
auto valPtr2 = task->getValue<float>();
if (valPtr2)
std::cout << *valPtr2 << '\n';
else
std::cout << "nullptr\n";
}