简体   繁体   English

指针到成员函数,模板和继承混合

[英]Pointer-to-member-func, template & inheritance mixup

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. 由于内部政策的原因,我无法使用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: 但是,当同事尝试从Callback类派生而不是使用typedef ,由于指针不兼容而导致编译错误:

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. 我尝试在Callback类中使用“好奇地重复使用的模板模式”,并设法使派生类正常工作,但是它破坏了使用typedef方法的代码。

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? 如何修改 Callback 类以使其同时适用于这两种情况,而又不需要类用户的额外工作?

It is unfortunate that you work for a company which advocates reinventing wheels and using bronze age C++. 不幸的是,您在一家提倡重塑车轮并使用青铜时代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. 我之前必须做类似的事情,不是因为公司政策,而是因为我们正在开发软件开发工具包,并且我们不希望SDK用户必须安装特定版本的boost。

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&). 如您所见,Callback对象本身不需要将对象类型作为模板参数传递,因此,只要该方法符合签名,它就可以指向任何类型的方法:void some_class :: some_method(const数据&)。 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. 将这些Callback对象的列表存储在一个能够调用所有对象的类中,您将获得一个带有已连接插槽的信号。

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. 为了不破坏typedef-way,可以为该参数提供默认值。 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) {}
};

Due to internal policy, I cannot use Boost. 由于内部政策的原因,我无法使用Boost。

Quit ;) 放弃 ;)

However, when a coworker tried to derive from the Callback class instead of using a typedef 但是,当同事尝试从Callback类派生而不是使用typedef时,

Shoot them. 射击他们。

I feel for you, I really do. 我为您感到,我确实如此。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM