سؤال

أحاول إنشاء كائن "رد فعل" عام يحتفظ ببيانات تعسفية واستدعاء وظائف الأعضاء في الفئات ذات الصلة. بسبب السياسة الداخلية ، لا يمكنني استخدام التعزيز.

يبدو كائن رد الاتصال هكذا:

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;
};

والصف الذي يعمل عليه:

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();
  }
};

يعمل الاختبار التالي كما هو متوقع:

Object1 obj1;
obj1.test();

حتى الان جيدة جدا.

ومع ذلك ، عندما حاول زميل في العمل الاستخلاص من 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();
  }
};

حاولت استخدام "نمط القالب المتكرر الغريب" في فئة رد الاتصال وتمكنت من الحصول على فئات مشتقة ، لكنها كسرت الرمز الذي استخدم typedef طريقة.

سؤالي هو:

كيف يمكنني تعديل Callback الفصل للعمل مع كلتا الحالتين ، ودون طلب عمل إضافي من جانب مستخدم الفصل؟

هل كانت مفيدة؟

المحلول

عليك أن تمرير نوع فئة المشتقة. لعدم كسر typedef-way ، يمكنك إعطاء هذه المعلمة قيمة افتراضية. شيء مثل ما يلي يجب أن يعمل

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 ...
};

بدلاً من ذلك ، يمكنك ببساطة الحصول على فئتين لهذا الغرض. واحد للميراث وواحد إذا لم ترث. الأول للميراث

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 ...
};

والثاني هو لعدم وجود. يمكنك الاستفادة من الفئة الأساسية لهذا

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

نصائح أخرى

من المؤسف أنك تعمل في شركة تدعو إلى إعادة اختراع العجلات واستخدام العصر البرونزي C ++. ومع ذلك ، بالنظر إلى الظروف والرمز الذي نشرته ، هناك حل بسيط إلى حد ما دون إجراء الكثير من التغييرات. اضطررت إلى القيام بأشياء مماثلة من قبل ليس بسبب سياسة الشركة ولكن لأننا كنا نطور مجموعة تطوير البرمجيات ولم نرغب في مطالبة مستخدمي SDK بإصدار محدد من Boost مثبت.

راجع للشغل ، أعتقد أن ما أنت عليه حقًا هو آلية إشارات وفتحات: أقترح دراستها إذا كنت ترغب في جعل هذا التصميم أكثر توافقًا مع الحلول الراسخة.

لقد اتخذت بعض الافتراضات الليبرالية: الأساسي هو أن البيانات لا يجب تخزينها في رد الاتصال ويمكن أن يتم تمريرها بدلاً من ذلك بواسطة المرسل. يجب أن يكون هذا تصميمًا أكثر مرونة. لا يزال الأمر بسيطًا للغاية - يمكنك أن تأخذ هذا كثيرًا (على سبيل المثال: رقم متغير من الوسائط وأنواع الإرجاع المخصصة ، فئة الإشارة ، الإخطارات المؤجلة ، إلخ).

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;
};

مثال الاستخدام:

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
}

كما ترون ، لا يتطلب كائن رد الاتصال نفسه أن يتم تمرير نوع الكائن كوسيطة قالب ، وبالتالي السماح له بالإشارة إلى طرق من أي نوع شريطة أن تتوافق الطريقة مع التوقيع: void some_class :: some_method (const (const بيانات&). قم بتخزين قائمة من كائنات رد الاتصال هذه في فئة قادرة على استدعاءها جميعًا ولديك إشارة مع فتحات متصلة.

بسبب السياسة الداخلية ، لا يمكنني استخدام التعزيز.

يترك ؛)

ومع ذلك ، عندما حاول زميل في العمل الاستخلاص من فئة رد الاتصال بدلاً من استخدام typedef

اطلق النار عليهم.

أشعر بك ، أنا حقا أفعل.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top