c++ - 在运行时选择要使用的 CRTP 实现

标签 c++ crtp

我想使用 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 来完成此操作?

最佳答案

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

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

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

您可以将这些东西隐藏在翻译单元中的匿名命名空间中,您可以在其中使用它们以更好地封装并避免使用工厂。

关于c++ - 在运行时选择要使用的 CRTP 实现,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/44849810/

相关文章:

java - Qt 使用多于 1 个参数调用 java 方法

c++ - 如何对数组使用 unique_ptr

c++ - 警告 : null destination pointer [-Wformat-overflow=] with GCC 11. 2.1

c# - 如何为类 C 语言创建 Visual Studio 语法突出显示

c++ - CRTP中的析构函数是怎么实现的?

c++ - 将 CRTP 与 SFINAE 混合

c++ - 如何为第三方遗留代码创建测试对象

c++ - C++ 中的多级继承 (CRTP)

c++ - CRTP + 特征类 : "no type named..."

c++ - 派生类作为模板参数有什么用?