문제

I am trying to create a generic "callback" object that will hold arbitrary data and invoke member functions of related classes. Due to internal policy, I cannot use Boost.

The callback object looks like this:

template<typename Object, typename Data>
class Callback
{
public:
  typedef void (Object::*PHandler)(Callback*);
  Callback(Object* obj, PHandler handler) : pObj(obj), pHandler(handler) {}
  Callback& set(PHandler handler) { pHandler = handler; return *this; }
  void run() { (pObj->*pHandler)(this); }

public:
  Data data;

protected:
  Object* pObj;
  PHandler pHandler;
};

And the class it works on:

struct Object1
{
  struct Data { int i; };

  typedef Callback<Object1, Data> Callback1;

  void callback(Callback1* pDisp) { printf("%cb\n", pDisp->data.i); }

  void test()
  {
    Callback1 cb(this, &Object1::callback);
    cb.data.i = 1;
    cb.run();
  }
};

The following test works as expected:

Object1 obj1;
obj1.test();

So far so good.

However, when a coworker tried to derive from the Callback class instead of using a typedef, they got compilation errors due to incompatible pointers:

struct Object2
{
  struct Data { int i; Data(int j) { i = j; } };

  class Callback2 : public Callback<Object2, Data>
  {
    Callback2(Object2* obj, PHandler handler, int i) : Callback(obj, handler) { data.i = i; }
  };

  void callback(Callback2* pDisp) { printf("%cb\n", pDisp->data.i); }

  void test()
  {
    Callback2 cb(this, &Object2::callback, 2);
    cb.run();
  }
};

I tried using the "curiously recurring template pattern" in the Callback class and managed to get derived classes working, but it broke code that used the typedef method.

My question is:

How can I modify the Callback class to work with both cases, and without requiring extra work on the part of the user of the class?

도움이 되었습니까?

해결책

You have to pass the class type of the derived. To not break the typedef-way, you can can give that parameter a default value. Something like the following should work

template<typename Object, typename Data, typename Derived = void>
class Callback;

namespace detail {
template<typename Object, typename Data, typename Derived>
struct derived { 
  typedef Derived derived_type;
};

template<typename Object, typename Data>
struct derived<Object, Data, void> { 
  typedef Callback<Object, Data, void> derived_type;
};
}

template<typename Object, typename Data, typename Derived>
class Callback : detail::derived<Object, Data, Derived>
{
  typedef typename 
    detail::derived<Object, Data, Derived>::derived_type
    derived_type;

  derived_type &getDerived() {
    return static_cast<derived_type&>(*this);
  }

public:
  // ... stays unchanged ...

  derived_type& set(PHandler handler) { 
    pHandler = handler; return getDerived(); 
  }
  void run() { (pObj->*pHandler)(&getDerived()); }

  // ... stays unchanged ...
};

Alternatively you can simply have two classes for this. One for inheritance and one if you don't inherit. The first is for inheritance

template<typename Object, typename Data, typename Derived>
class CallbackBase
{
  typedef Derived derived_type;

  derived_type &getDerived() {
    return static_cast<derived_type&>(*this);
  }

public:
  // ... stays unchanged ...

  derived_type& set(PHandler handler) { 
    pHandler = handler; return getDerived(); 
  }
  void run() { (pObj->*pHandler)(&getDerived()); }

  // ... stays unchanged ...
};

And the second is for non-inheritance. You can make use of the base-class for this

template<typename Object, typename Data>
struct Callback : CallbackBase<Object, Data, Callback<Object, Data> > {
  Callback(Object* obj, PHandler handler) : Callback::CallbackBase(obj, handler) {}
};

다른 팁

It is unfortunate that you work for a company which advocates reinventing wheels and using bronze age C++. However, given the circumstances and the code you posted, there is a fairly simple solution without making too many changes. I've had to do similar things before not because of company policy but because we were developing a software development kit and we did not want to require that users of the SDK have a specific version of boost installed.

BTW, I believe what you are really after is a signals and slots mechanism: I suggest studying it if you want to make this design a bit more conforming to well-established solutions.

I took a few liberal assumptions: the primary one being that Data does not have to be stored in the callback and can instead be passed by the sender. This should be a more flexible design. It is still very simple - you can take this a lot further (ex: variable-number of arguments and custom return types, a signal class, deferred notifications, etc).

template<typename Data>
class CallbackInterface
{
public: 
    virtual ~CallbackInterface() {}
    virtual void run(const Data& data) = 0;
    virtual CallbackInterface* clone() const = 0;
};

template<typename Object, typename Data>
class CallbackMethod: public CallbackInterface<Data>
{
public:
    typedef void (Object::*PHandler)(const Data&);
    CallbackMethod(Object* obj, PHandler handler) : pObj(obj), pHandler(handler) {}
    virtual void run(const Data& data) { (pObj->*pHandler)(data); }
    virtual CallbackInterface* clone() const {return new CallbackMethod(*this); }

protected:
    Object* pObj;
    PHandler pHandler;
};

template <class Data>
class Callback
{
public:
    template <class Object>
    Callback(Object* obj, void (Object::*method)(const Data&) ):
        cb(new CallbackMethod<Object, Data>(obj, method)) 
    {
    }

    Callback(const Callback& other): cb(other.cb->clone() )
    {

    }

    Callback& operator=(const Callback& other) 
    {
        delete cb;
        cb = other.cb->clone();
        return *this;
    }

    ~Callback() 
    {
        delete cb; 
    }

    void operator()(const Data& data) const
    {
       cb->run(data);
    }

private:
    CallbackInterface<Data>* cb;
};

Example usage:

struct Foo
{
    void f(const int& x)
    {
        cout << "Foo: " << x << endl;
    }
};

struct Bar
{
    void f(const int& x)
    {
        cout << "Bar: " << x << endl;
    }
};

int main()
{
    Foo f;
    Callback<int> cb(&f, &Foo::f);
    cb(123); // outputs Foo: 123

    Bar b;
    cb = Callback<int>(&b, &Bar::f);
    cb(456); // outputs Bar: 456
}

As you can see, the Callback object itself does not require the object type to be passed in as a template argument, thereby allowing it to point to methods of any type provided that the method conforms to the signature: void some_class::some_method(const Data&). Store a list of these Callback objects in a class capable of calling all of them and you have yourself a signal with connected slots.

Due to internal policy, I cannot use Boost.

Quit ;)

However, when a coworker tried to derive from the Callback class instead of using a typedef

Shoot them.

I feel for you, I really do.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top