【问题标题】:Alternative to conversion constructor for template parameter替代模板参数的转换构造函数
【发布时间】:2014-10-23 20:46:39
【问题描述】:

我在 C++ 应用程序中使用 GLFW 进行窗口和输入管理。为了使用成员函数作为关键事件的回调,我使用了答案 here 中建议的单例。

但是,我需要实现不同的输入处理程序。我的方法是使用 Singleton 作为实际输入处理程序 (controller_) 的包装器,以允许多态性。但是,对于要实例化的 Singleton,基类不能是抽象的。该解决方案涉及使用CRTP,以便能够从基类的实现中调用特定的输入处理方法。

  template <class T>
  class Controller : public BC{ //BC is just for using this class as a template parameter itself
    public:
    Controller(){}                    
    Controller(Controller &controller){
      controller_ = &controller;
    }

    static Controller& getInstance(Controller *controller){
      static Controller instance(*controller);
      return instance;
    }   

    //This is the key move, where the concrete implementation is invoked.
    static void keyCallback(GLFWwindow* window, int key, int scancode, int action, int mods){
      Controller *aux;
      (static_cast<T>(getInstance(aux))).keyCallbackImpl(window, key, scancode, action, mods); 
    }

    //Stub to be overridden by the concrete input handler        
    virtual void keyCallbackImpl(GLFWwindow* window, int key, int scancode, int action, int mods){}

    //This is the wrapped input handler
    Controller *controller_;   

  };

这很好用。但是,派生类存在问题。为了能够执行强制转换,我必须定义一个转换构造函数。

SMController(Controller<SMController> &c){
  controller_ = c.controller_;
  std::cout << "Constructor" << std::endl;
}

这不方便有两个原因:

  • 从 Controller 派生的用户必须明确定义此构造函数
  • 每次按键时都构建一个新的包装器似乎很昂贵

使用这种设计是否可以替代这种转换?

编辑: 我最终接受了 T.C. 的提议,尽管略有不同。由于我需要具有自己的一组参数的子类,因此能够在构造函数中提供它们是理想的。初始化单例的单独调用很容易出错,因为它可能使用错误的模板参数来完成,或者只是被遗忘了。

为了能够在一次调用中使用其参数和对应的单例实例化专用对象,我继续使用 CRTP 并将这个构造函数添加到基类中:

Controller<T>(){
  T::getInstance((T*)this);
}

现在,只需一个电话,我就能得到所需的一切:

std::shared_ptr<BaseController> c(new SMController(params_, window_));

【问题讨论】:

  • 抱歉,如果 aux 未初始化,getInstance(aux) 应该如何工作?我错过了什么吗?
  • 单例是静态的,所以它只在第一次调用getInstance() 时被实例化(在其他地方完成)。
  • 典型的 CRTP 实现将 this 从 Base&lt;T&gt; * 转换为 T *,或将 *this 从 Base&lt;T&gt; &amp; 转换为 T &amp;。
  • 很遗憾,我是在静态环境中投射的。
  • @broncoAbierto 同样的原则也适用。我是否正确理解 controller_ 应该存储指向派生的指针?

标签: c++ implicit-conversion crtp


【解决方案1】:

实际上,我并没有真正看到在这里使用 CRTP 的意义。直接存储指向模板参数的指针有什么问题?

template <class T>
class Controller : public BC { 
public:
  static Controller& getInstance(T * target = nullptr){
    static Controller instance(target);
    return instance;
  }   

  static void keyCallback(GLFWwindow* window, int key, int scancode,
                          int action, int mods){
    getInstance().target_->keyCallbackImpl(window, key, scancode, action, mods); 
  }
private:              
  Controller(T* target) : target_(target) { }   
  //This is the wrapped input handler
  T* target_;
};

【讨论】:

  • 由于某种原因,目标似乎永远不会被初始化。我遇到了段错误。
  • 另外,控制器的每个专业化必须能够访问不同的参数。
  • @bronco 您仍然需要使用指向目标的指针调用一次 getInstance 来初始化它....
  • 是的,我愿意,但仍然如此。我在各个点打印地址,指针似乎已正确初始化。也许这与我如何在外部管理它有关。这很奇怪,因为对象在堆中,我可以从其他作用域调用它的其他方法。此外,我在这些范围内使用的指针指向的地址与keyCallback 方法中的地址相同……我明天再检查一下。
  • 好的,最后段错误是在代码的其他地方引起的。我最终使用了您的想法,但我继续使用 CRTP,因为它允许我在一次调用中实例化具有特定参数的对象及其 Singleton。我将编辑问题以获取详细信息。谢谢。
【解决方案2】:

据我了解,您想要的是使用 Controller 实例的其中一个子类来处理输入来初始化实例的能力。为此,您不需要 CRTP,而且,您根本不需要模板。见代码:

// Controller.h
class Controller {
    // disallow copying
    Controller(const Controller&) = delete;
    Controller& operator=(const Controller&) = delete;

    //This is the wrapped input handler
    static Controller* instance_;

protected:    
    Controller() = default;

public:
    virtual ~Controller() = default;

    static void initInstance(Controller* controller) {
        // ensures you set instance only once
        // you can also put run-time assert here
        static Controller* instance = controller;
        instance_ = instance;
    }

    static Controller* getInstance() {
        return instance_;
    }

    //This is the key move, where the concrete implementation is invoked.
    static void keyCallback(GLFWwindow* window, int key, int scancode, int action, int mods){
        getInstance()->keyCallbackImpl(window, key, scancode, action, mods); 
    }

    //Stub to be overridden by the concrete input handler        
    virtual void keyCallbackImpl(GLFWwindow* window, int key, int scancode, int action, int mods) = 0;
};

class SMController : public Controller {
public:
    virtual void keyCallbackImpl(GLFWwindow* window, int key, int scancode, int action, int mods) override {
        std::cout << "in SMController::keyCallbackImpl()\n";
    }
};


// Controller.cpp
Controller* Controller::instance_ = nullptr;

// test.cpp
int main()
{
    SMController smc;
    Controller::initInstance(&smc);
    Controller::keyCallback(nullptr, 0, 0, 0, 0);
}

【讨论】:

  • 如果我没看错,OP 似乎希望能够使用不同类型的控制器,因此是模板。
  • @broncoAbierto 那么 T.C. 的回答就可以了。
猜你喜欢
  • 2019-04-19
  • 1970-01-01
  • 2014-05-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多