I am using GLFW for window and input management in a C++ application. In order to use a member function as a callback for key events, I used a singleton as suggested in an answer here.
However, I need to implement different input handlers. My approach is to use the Singleton as a wrapper for the actual input handler (controller_) to allow for polymorphism. However, for the Singleton to be instantiated the base class cannot be abstract. The solution involved using the CRTP in order to be able to invoke the specific input handling method from the implementation of the base class.
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_;
};
This works fine. However, there is a problem with the derived class. In order to be able to perform the cast, I must define a conversion constructor.
SMController(Controller<SMController> &c){
controller_ = c.controller_;
std::cout << "Constructor" << std::endl;
}
This is inconvenient for two reasons:
- Users deriving from Controller must explicitly define this constructor
- The construction of a new wrapper every time a key is pressed seems costly
Is there an alternative to this conversion using this design?
EDIT: I ended up going with T.C.'s proposal, although with a slight difference. Since I needed subclasses with their own set of parameters, it's ideal to be able to provide them in a constructor. The separate call to initialize the singleton is error prone, because it could be done with the wrong template argument, or simply forgotten.
In order to enable the instantiation of the specialized object with its parameters and its corresponding singleton in one call, I kept using the CRTP and I added this constructor to the base class:
Controller<T>(){
T::getInstance((T*)this);
}
Now, with just one call I get everything I need:
std::shared_ptr<BaseController> c(new SMController(params_, window_));