【问题标题】:Choose at runtime which CRTP implementation to use在运行时选择要使用的 CRTP 实现
【发布时间】:2017-06-30 15:17:35
【问题描述】:

我想使用 CRTP 惯用语来使用静态多态性,同时能够在运行时选择要使用的实现。我举个例子:

我有一些类负责计算东西:

template<typename Implementation>
class FooInterface {
public:
  void compute(){
    (static_cast<Implementation*>(this))->compute();
  }
};

class FooForward : public FooInterface<FooForward> {
public:
  void compute(){
    //do stuff
  }
};

class FooBackward : public FooInterface<FooBackward> {
public:
  void compute(){
    //do other stuff
  }
};

template<typename Implementation>
class BarInterface {
public:
  void eval(){
    (static_cast<Implementation*>(this))->eval();
  }
};

class BarForward : public BarInterface<BarForward> {
public:
  void eval(){
    //do something
  }
};

class BarBackward : public BarInterface<BarBackward> {
public:
  void eval(){
    //do something else
  }
};

现在我想将这些对象用作另一个类的成员,我们称之为Model,并在循环中使用它们:

template<typename Foo, typename Bar>
class Model {
private:
  Foo* foo_;
  Bar* bar_;
  int max_iter_;

public:
  Model<Foo, Bar>(int max_iter) : max_iter_(max_iter){
    foo_ = new Foo();
    bar_ = new Bar();
  }

  void solve(){
    for(int i = 0; i < max_iter_; ++i){
      foo_->compute();
      bar_->eval();
    }
  }
};

请注意,函数 Model::solve() 在我的应用程序中执行大量迭代,性能至关重要,因此使用 CRTP 而不是动态多态性来避免虚函数调用并启用编译器的函数内联。

现在,当我想让用户决定在运行时使用 FooInterfaceBarInterface 的哪个实现时,我的问题就出现了。在我的main.cpp 我有:

int main(int argc, char** argv){
  /*
   * Here an input file is read into a map which looks like this
   * std::map<std::string, std::string> settings
   */
  // Here I need a way to choose, based on settings, what will Foo and Bar be
  Model<Foo, Bar> model;
  model.solve();
}

我曾想过一种可以返回正确 Model 的工厂,但我不知道返回类型可能是什么,而且我想象的方式并不方便,因为在我的应用程序中我有超过 2 个模板参数然后组合的数量变得非常大

class Factory{
  /*type?*/ createModel(std::map<std::string, std::string> settings){
    if ((settings["foo"] == "fwd") && (settings["bar"] == "fwd")){
      Model<FooForward, BarForward>* model = new Model<FooForward, BarForward>();
      return model;
    }
    else if ((settings["foo"] == "fwd") && (settings["bar"] == "bwd")){
      Model<FooForward, BarBackward>* model = new Model<FooForward, BarBackward>();
      return model;
    }
    else if ((settings["foo"] == "bwd") && (settings["bar"] == "fwd")){
      Model<FooBackward, BarForward>* model = new Model<FooBackward, BarForward>();
      return model;
    }
    else {
      Model<FooBackward, BarBackward>* model = new Model<FooBackward, BarBackward>();
      return model;
    }
  }
};

按照我的想象,所有模板组合都将被编译,用户可以在运行时选择使用哪一个。有没有办法使用 CRTP 来实现这一点?

【问题讨论】:

    标签: c++ crtp


    【解决方案1】:

    对于工厂方法,我认为没有办法定义单一类型,因为在编译时需要类型信息,而实际设置只有在程序执行期间才能知道。

    但是,如果您使用变体,则可以将所有可能的返回类型组合成一个。然后可以通过工厂方法返回此类型:

    class Factory{
    
    public:
    
        using ModelVariant = boost::variant
        <
            Model< FooBackward , BarBackward > ,
            Model< FooBackward , BarForward > ,
            Model< FooForward , BarBackward > ,
            Model< FooForward , BarForward >
        >;
    
        static ModelVariant createModel(std::map<std::string, std::string> settings , int i)
        {
            if ((settings["foo"] == "fwd") && (settings["bar"] == "fwd")){
              Model<FooForward, BarForward> model = Model<FooForward, BarForward>(i);
              return model;
            }
            else if ((settings["foo"] == "fwd") && (settings["bar"] == "bwd")){
              Model<FooForward, BarBackward> model = Model<FooForward, BarBackward>(i);
              return model;
            }
            else if ((settings["foo"] == "bwd") && (settings["bar"] == "fwd")){
              Model<FooBackward, BarForward> model = Model<FooBackward, BarForward>(i);
              return model;
            }
            else// ((settings["foo"] == "bwd") && (settings["bar"] == "bwd"))
            {
              Model<FooBackward, BarBackward> model = Model<FooBackward, BarBackward>(i);
              return model;
            }
        }
    };
    

    但现在您需要一个访问者来实际调用所需的solve() 方法:

    auto model { Factory::createModel( settings , 1 ) };
    
    boost::apply_visitor( [ ]( auto & m ){ m.solve(); } , model );
    //     > FooForward::compute()
    //     > BarBackward::eval()
    

    Live at Coliru

    此外,如果您不断添加不同的 FooBar 实现,那么如果不采用某种模板元编程,那么很快将很难维持。


    原答案:

    也许你可以使用一个静态模板变量和一个简单的函数:

    std::map< std::string , std::string > settings
    {
        { "foo" , "fwd" } ,
        { "bar" , "bwd" }
    };
    
    template< typename F , typename B>
    static Model< F , B > m( 1 );
    
    void solve()
    {
        if ((settings["foo"] == "fwd") && (settings["bar"] == "fwd")){
          m<FooForward, BarForward>.solve();
        }
        else if ((settings["foo"] == "fwd") && (settings["bar"] == "bwd")){
          m<FooForward, BarBackward>.solve();
        }
        else if ((settings["foo"] == "bwd") && (settings["bar"] == "fwd")){
          m<FooBackward, BarForward>.solve();
        }
        else// ((settings["foo"] == "bwd") && (settings["bar"] == "bwd"))
        {
          m<FooBackward, BarBackward>.solve();
        }
    }
    
    int main()
    {
        // Load settings somehow
    
        solve(); // > FooForward::compute()
                 // > BarBackward::eval()
    
    }
    

    Live at Coliru

    您可以将这些东西隐藏在翻译统一体中的匿名命名空间中,以便更好地封装并避免使用工厂。

    【讨论】:

    • 非常感谢您的回答,我会试试这个并让您知道。至于维护部分,这就是我害怕的。我目前对FooBar 有两个以上的实现,Model 中有两个以上的成员,这大大增加了可能的组合数量。你认为某种模板元编程可以帮助我处理所有的组合还是重新考虑我的设计更好?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多