Выберите тип членов класса во время выполнения - PullRequest
0 голосов
/ 07 сентября 2018

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

#include <iostream>

// One of the modules. Code will consist of multiple of these modules.
template <class T, class U>
class Module1 {
public:
    T subModule1;
    U subModule2;
};

class Add {
public:
    double Apply(double a) {
        return a + x;
    }

private:
    double x = 2.5;
};

class Multiply {
public:
    double Apply(double a) {
        return a * pi;
    }

private:
    double pi = 3.14;
};

class Divide {
public:
    double Apply(double a) {
        return a / pi;
    }

private:
    double pi = 3.14;
};

int main(int argc, char* argv[])
{
    // User input to determine the used submodules
    // Here the user decides to use the Add and Multiply submodules.
    Module1<Add, Multiply> module1;

    std::cout << module1.subModule1.Apply(3) + module1.subModule2.Apply(2) << std::endl;
}

Но как я могу реализовать решение, какой из подмодулей следует использовать? Например, если пользователь хочет использовать Divide и Add, я должен создать Module1 как

Module1<Divide, Add> module1;

Будет много разных подмодулей, поэтому ветвление кода с помощью if также невозможно. Я начинаю сомневаться, что это возможно с шаблонами. Вы знаете лучший способ сделать это? Обратите внимание, что реализация также должна быть очень производительной.

Ответы [ 3 ]

0 голосов
/ 07 сентября 2018

Используйте простой полиморфизм.

class Base { 
public:
    virtual ~Base() = default;
    virtual double Apply(double a) = 0;
};

class Add : public Base { 
public:
    double Apply(double a) override {}
};

class Multiply : public Base { 
public:
    double Apply(double a) override {}
};

class Module
{
public:
    void addModule(std::unique_ptr<Base> module) { 
        modules.push_back(std::move(module)); 
    }

    void Apply(double a) {
        for (const auto& module : modules)
            module->Apply(a);
    }

    std::vector<std::unique_ptr<Base>> modules;
}

Module m;
m.addModule(std::make_unique<Add>());
m.addModule(std::make_unique<Divide>());
0 голосов
/ 07 сентября 2018

Возможное решение состоит в том, чтобы составить все возможные комбинации и выбрать правильную:

template <typename T1, T2>
void f() {
    Module<T1, T2> module;

    std::cout << module.subModule1.Apply(3) + module.subModule2.Apply(2) << std::endl;
}

int main()
{
     int type1 = userChoice();
     int type2 = userChoice();
     std::array<void(*)(), 9> fs = {
         &f<Add, Add>,      &f<Add, Multiply>,      &f<Add, Divide>, 
         &f<Multiply, Add>, &f<Multiply, Multiply>, &f<Multiply, Divide>, 
         &f<Divide, Add>,   &f<Divide, Multiply>,   &f<Divide, Divide>, 
     }

    fs[type1 + 3 * type2]();
}

Массив может быть даже создан с помощью некоторого метапрограммирования.

Но я думаю, что простой полиморфизм (как ответ Дана) достаточно хорош.

0 голосов
/ 07 сентября 2018

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

Если у вас есть закрытый набор возможных подмодулей, вы можете использовать std::variant. Это в основном типобезопасный union:

using SubmoduleVariant = std::variant<Add, Subtract, Multiply, Divide>;

class Module1 {
public:
    SubmoduleVariant subModule1;
    SubmoduleVariant subModule2;
};

// ...

Module1 m;

     if(userInput == 0) { m.subModule1 = Add{}; }
else if(userInput == 1) { m.subModule1 = Subtract{}; }
else if(userInput == 2) { m.subModule1 = Multiply{}; }
else if(userInput == 3) { m.subModule1 = Divide{}; }

Если вы заранее не знаете набор возможных типов, но все они соответствуют одному и тому же интерфейсу, вы можете использовать функции virtual и базовый класс.


Если вы заранее не знаете набор возможных типов, и они не соответствуют одному и тому же интерфейсу, вы можете использовать std::any, который по сути является оберткой над объектом любой тип ".

...