0

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_));
Community
  • 1
  • 1
cangrejo
  • 2,189
  • 3
  • 24
  • 31
  • Sorry, how is `getInstance(aux)` supposed to work if aux is not initialized? Am I missing something? – Marco A. Aug 29 '14 at 19:45
  • The singleton is static, so it is only instantiated the first time `getInstance()` is invoked (which is done elsewhere). – cangrejo Aug 29 '14 at 19:49
  • The typical CRTP implementation casts `this` from `Base *` to `T *`, or `*this` from `Base &` to `T &`. – T.C. Aug 29 '14 at 20:01
  • Sadly, I'm casting in a static context. – cangrejo Aug 29 '14 at 20:03
  • @broncoAbierto The same principle applies. Am I understanding correctly that `controller_` should store a pointer-to-derived? – T.C. Aug 29 '14 at 20:05
  • This sounds too complicated solution for the problem. Instead of asking for an alternative for a detail of the design, ask for an alternative design. – Cheers and hth. - Alf Aug 29 '14 at 20:51

2 Answers2

2

Actually, I don't really see the point of using CRTP here. What's the problem with just storing the pointer to the template argument directly?

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_;
};
T.C.
  • 133,968
  • 17
  • 288
  • 421
  • For some reason, it looks like the target is never getting initialized. I get a seg fault. – cangrejo Aug 29 '14 at 21:10
  • Also, each specialization of the controller must have access to different parameters. – cangrejo Aug 29 '14 at 21:20
  • @bronco You still need to call getInstance once with a pointer to the target to initialize it.... – T.C. Aug 29 '14 at 23:12
  • Yes, I do, but still. I'm printing addresses at various points and the pointer seems to be properly initialized. Maybe it's related to how I manage it externally. It's weird, because the object is in the heap, and I can invoke other methods on it from other scopes. Additionally, the pointer I use in those scopes points to the same address as the one in the `keyCallback` method... I'll check it out again tomorrow. – cangrejo Aug 30 '14 at 01:20
  • Ok, finally the segfault was caused elsewhere in the code. I ended up using your idea, but I kept using CRTP since it allows me to instantiate an object with specific parameters and its Singleton in one call. I'll edit the question for details. Thanks. – cangrejo Aug 30 '14 at 11:05
1

As I understand, what you want is ability to initialize Controller instance with one of it's subclasses to handle input. For this you don't need CRTP and moreover, you don't need templates at all. See the code:

// 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);
}
Anton Savin
  • 40,838
  • 8
  • 54
  • 90