Как указать общий тип шаблонного псевдонима в контейнере - PullRequest
0 голосов
/ 30 августа 2018

У меня есть класс Задача:

template <typename T>
class Task {

    Task(const std::function<T()>& func) 
        : m_func(func)
    {
        // some stuff here
    }

    std::shared_ptr<T> getValue() {
        return m_value;
    }

    void execute() {
        m_value = std::make_shared<T>(m_func());
    }


    std::shared_ptr<T> m_value;  
    std::function<T()> m_func;  
}

Теперь я хочу связать этот класс Task с shared_ptr, поэтому я делаю следующее ...

template <typename T> using TaskPtr = std::shared_ptr<Task<T> >;

У меня есть другой класс, который будет хранить контейнер TaskPtr, я бы хотел, чтобы потребитель API указывал T при вызове addTask следующим образом.

Class X {
    // some boiler plate code
    template <typename T> 
    addTask(TaskPtr<T> 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<T> > m_queue; 
}

Мне было интересно, как лучше всего это сделать. Этот код дает мне ошибку, что T не определено. Duh! Мне нужно добавить template <tyepname T> выше моего m_queue определения, это имеет смысл. Когда я это делаю, я получаю, что я помещаю ключевое слово typedef в неправильном месте Когда я удаляю объявление шаблона и T, чтобы просто иметь std::queue<Taskptr> m_queue;, он говорит мне, что мне не хватает аргумента шаблона. Это имеет смысл, за исключением того, что я не понимаю, куда это должно идти.

Я искал ответ и не смог ничего найти. Какова правильная синтаксическая реализация того, что я пытаюсь сделать?

1 Ответ

0 голосов
/ 30 августа 2018

Ошибка по адресу:

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