Шаблонный базовый класс еще имеет общий указатель базового класса? - PullRequest
1 голос
/ 31 марта 2019

Я пытаюсь построить график выполнения, в котором каждый узел может создавать несколько выходных данных различных типов и использовать несколько входных данных различных типов.Данные передаются между узлами через очереди.

Чтобы соединить 2 узла, тип входа одного узла должен быть подключен к тому же типу вывода другого узла.Например, попытка подключить выход int одного узла к входу double другого узла приведет к ошибке компиляции.

Все узлы будут производными от базового класса, который имеет execute()метод, который читает из различных типов ввода и записывает в различные типы вывода.В настоящее время у меня есть что-то вроде этого -

struct Node {
    virtual void execute() = 0;
};

struct IntegerGeneratorNode: public Node {
    void execute() {
        while(some_condition_is_not_met) {
           // write() will do std::queue<int>::push();
           int_out.write(some_rand_integer);
        }
    }
    Output<int> int_out;
};

struct FloatGeneratorNode: public Node {
    void execute() {
        while(some_condition_is_not_met) {
           // write() will do std::queue<float>::push();
           float_out.write(some_rand_float);
        }
    }
    Output<float> float_out;
};

struct SinkNode: public Node {
    void execute() {
        while(some_condition) {
           int val = int_inp.read(); // invokes queue<int>::front()+pop()
           float f_val = float_inp.read();
           // Do something with val and f_val.
        }
    }
    Input<int> int_inp;
    Input<float> float_inp;
};

Input<T> - шаблонный класс, который имеет queue типа T.Output<T> является шаблонным классом, который имеет указатель на queue типа T.Чтобы соединить 2 узла, я делаю что-то вроде -

Node *int_node = IntegerGeneratorNode();
Node *float_node = FloatGeneratorNode();
Node *sink_node = SinkNode();

int_node.int_out.connect(sink_node.int_inp);
float_node.float_out.connect(sink_node.float_inpt);

std::thread int_thread([](Node *node){ node->execute(); }, int_node);
std::thread float_thread([](Node *node){ node->execute(); }, float_node);
std::thread sink_thread([](Node *node){ node->execute(); }, sink_node);

Это работает нормально, но у меня есть другое требование - выполнить некоторую операцию на ВСЕХ Input<T> с Node до того, как метод executeназывается - Input<T>::doSomePreProcessing().Как видите, именованные переменные не масштабируются.Вы должны добавить .doSomething на ВСЕ переменные, которые у вас есть.Мне нужен какой-то цикл.

Одна идея состоит в том, чтобы иметь tuple из Input типов и повторять кортеж, используя C ++ 17 std::apply.Но у меня есть еще одно важное требование: производные классы являются клиентским кодом, а базовый класс - структурным кодом.Вся предварительная обработка должна выполняться из базового класса, чтобы уменьшить нагрузку на производные классы.Для этого мне нужно переместить кортеж в базовый класс следующим образом:

template<typename T>
struct Node {
    virtual void execute() = 0;
    void doExecute {
        preprocess(some_tuple);
        execute();
    }
    T inputs() { return input_tuple; }
    T input_tuple;
};

template<typename T>
struct SinkNode: public Node<T>{
   ...
};

// Call site.
SinkNode<std::tuple<int,float>> sink_node;

2 побочных эффекта произошло из-за этого изменения

  1. Базовый класстеперь я не могу иметь общий указатель базового класса, который требуется для вызова метода doExecute в узлах графа.

  2. connect трудно реализовать.В идеале я ищу

template<typename T>
void connect(Node* src, Node* dest) {
    std::get<T>(src.outputs()).connect(std::get<T>(dest.inputs()));
}

Но поскольку Node* больше не доступен, я не могу этого сделать.

Как мне все сделатьОбработка на входах в базовом классе еще имеет общий указатель базового класса?Другими словами, это не шаблонно?

1 Ответ

1 голос
/ 31 марта 2019

На ум приходит несколько подходов:

  1. Разделение virtual void execute() = 0 на отдельный базовый класс, из которого вы затем получаете различные, шаблонные базовые классы.
  2. Применение шаблона декоратора: Просто создайте класс Node, который обернет другой класс Node.Когда вызывается внешний execute(), предварительно обработайте ввод и затем вызовите внутренний execute().
  3. Использовать множественное наследование.Один базовый класс может предоставлять интерфейс execute(), другой - интерфейс предварительной обработки.Это похоже на первый вариант, только то, что две части расположены не друг над другом, а рядом друг с другом на одном уровне.
...