【问题标题】:Choose type of class members at run time在运行时选择类成员的类型
【发布时间】:2018-09-07 10:49:40
【问题描述】:

我正在尝试构建一个包含其他类作为成员的模块化类,用户可以在运行时指定这些类的类型。我尝试使用模板来实现这一点,但意识到这可能不是正确的方法。这是一个最小的工作示例:

#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 and Add 我必须将 Module1 创建为

Module1<Divide, Add> module1;

会有很多不同的子模块,所以用 if 分支代码也是不可能的。我开始怀疑模板是否可行。您知道实现此目的的更好方法吗?请注意,实现也应该非常高效。

【问题讨论】:

    标签: c++ class templates metaprogramming


    【解决方案1】:

    模板用于编译时多态,而您需要运行时多态。

    如果您有一组封闭的可能子模块,您可以使用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,它基本上是对“任何类型的对象”的包装。

    【讨论】:

    • 这个方法看起来正是我需要的。但是,我怎样才能访问成员函数呢?编译上述代码在 cout 行中产生以下错误:'using SubmoduleVariant = class std::variant {aka class std::variant}' has no member named 'Apply ' std::cout
    • @sp1ke:查看std::visit 的文档。
    【解决方案2】:

    使用简单的多态性。

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

    【讨论】:

    • 我最终实现了这个方法。我也尝试了@VittorioRomeo 建议的方法,可以确认它工作正常。这两种方法都将我的程序的运行时间增加了大约 2%。但由于在我的情况下更容易维护,我决定实施这个解决方案。
    【解决方案3】:

    可能的解决方案是进行所有可能的组合,并选择正确的组合:

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

    数组甚至可以通过一些元编程来创建。

    但我认为简单的多态性(如丹的回答)就足够了。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-04-18
      • 1970-01-01
      • 1970-01-01
      • 2017-03-17
      • 1970-01-01
      • 2018-06-22
      相关资源
      最近更新 更多