أعتقد أن معالجة الحدث كطريقة للتفاعل بين الكائنات في OOP معروف للجميع تقريبًا ممن لمسوا OOP مرة واحدة على الأقل. على الأقل ، هذا النهج مريح للغاية في مجموعة واسعة جدًا من المهام ، في رأيي. في العديد من لغات البرمجة ، يتم تضمين محرك معالجة الحدث ؛ ومع ذلك ، في C ++ لا يوجد مثل هذه الآلية. دعونا نرى ما يمكنك القيام به حيال ذلك.
مقدمة موجزة
الحدث هو شيء يمكن أن يحدث لشيء ما تحت ظروف معينة (على سبيل المثال ، مع زر عند النقر عليه بالماوس). قد تحتاج الكيانات الأخرى إلى أن تدرك ذلك ؛ ثم
اشتركوا في الحدث . في هذه الحالة ، عند وقوع حدث ، يتم استدعاء
معالج كائن تابع لجهة خارجية مشترك في الحدث ؛ وبالتالي ، لديه الفرصة لتنفيذ بعض التعليمات البرمجية ، أي الرد على حدث. وبالمثل ، يمكن للكائن
إلغاء الاشتراك من حدث إذا لم يعد يريد الرد عليه. نتيجة لذلك ، لدينا العديد من الأشياء التي يمكن ربطها ببعضها البعض من خلال أحداث أحدها ورد الفعل على أحداث الآخرين.
شيء من هذا القبيل ، على الرغم من أن الجميع يعرفون ذلك.
أبسط تنفيذ
قد يبدو من السهل تنفيذ مثل هذا السلوك. وقد يبدو مثل هذا:
template<class ...TParams> class AbstractEventHandler { public: virtual void call( TParams... params ) = 0; protected: AbstractEventHandler() {} };
template<class ...TParams> class TEvent { using TEventHandler = AbstractEventHandler<TParams...>; public: TEvent() : m_handlers() { } ~TEvent() { for( TEventHandler* oneHandler : m_handlers ) delete oneHandler; m_handlers.clear(); } void operator()( TParams... params ) { for( TEventHandler* oneHandler : m_handlers ) oneHandler->call( params... ); } void operator+=( TEventHandler& eventHandler ) { m_handlers.push_back( &eventHandler ); } private: std::list<TEventHandler*> m_handlers; };
template<class TObject, class ...TParams> class MethodEventHandler : public AbstractEventHandler<TParams...> { using TMethod = void( TObject::* )( TParams... ); public: MethodEventHandler( TObject& object, TMethod method ) : AbstractEventHandler<TParams...>(), m_object( object ), m_method( method ) { assert( m_method != nullptr ); } virtual void call( TParams... params ) override final { ( m_object.*m_method )( params... ); } private: TObject& m_object; TMethod m_method; }; template<class TObject, class ...TParams> AbstractEventHandler<TParams...>& createMethodEventHandler( TObject& object, void( TObject::*method )( TParams... ) ) { return *new MethodEventHandler<TObject, TParams...>( object, method ); } #define METHOD_HANDLER( Object, Method ) createMethodEventHandler( Object, &Method ) #define MY_METHOD_HANDLER( Method ) METHOD_HANDLER( *this, Method )
يجب أن يكون تطبيق هذه الحالة بالشكل:
class TestWindow { . . . public: TEvent<const std::string&, unsigned int> onButtonClick; . . . }; class ClickEventHandler { . . . public: void testWindowButtonClick( const std::string&, unsigned int ) { ... } . . . }; int main( int argc, char *argv[] ) { . . . TestWindow testWindow; ClickEventHandler clickEventHandler; testWindow.onButtonClick += METHOD_HANDLER( clickEventHandler, ClickEventHandler::testWindowButtonClick ); . . . }
وبطبيعة الحال ، لن تكون طريقة المعالج (-الوظيفة-عضو في فئة) هي النوع الوحيد من المعالجات ، ولكن المزيد عن ذلك لاحقًا.
يبدو أن كل شيء مريح وصغير ورائع. ولكن في حين أن هناك عدد من أوجه القصور.
مقارنة معالج
لتنفيذ إلغاء الاشتراك من حدث ، من الضروري إضافة إمكانية المقارنة إلى المعالج (بواسطة
== و
! == ). تعتبر تلك المعالجات التي تستدعي نفس الأسلوب (وظيفة عضو لفئة) من نفس الكائن (أي نفس مثيل نفس الفئة) متساوية.
template<class ...TParams> class AbstractEventHandler { . . . using MyType = AbstractEventHandler<TParams...>; public: bool operator==( const MyType& other ) const { return isEquals( other ); } bool operator!=( const MyType& other ) const { return !( *this == other ); } protected: virtual bool isEquals( const MyType& other ) const = 0; . . . };
template<class TMethodHolder, class ...TParams> class MethodEventHandler : public AbstractEventHandler<TParams...> { . . . using TMethod = void( TObject::* )( TParams... ); protected: virtual bool isEquals( const AbstractEventHandler<TParams...>& other ) const override { const MyType* _other = dynamic_cast<const MyType*>( &other ); return ( _other != nullptr && &m_object == &_other.m_object && m_method == _other.m_method ); } private: TObject& m_object; TMethod m_method; . . . };
ثم سنتمكن من إزالة المعالجات من اشتراك الحدث. في هذه الحالة ، من الضروري حظر إضافة نفس المعالجات (المتساوية).
template<class ...TParams> class TEvent { . . . using TEventHandler = AbstractEventHandler<TParams...>; using TEventHandlerIt = typename std::list<TEventHandler*>::const_iterator; public: bool operator+=( TEventHandler& eventHandler ) { if( findEventHandler( eventHandler ) == m_handlers.end() ) { m_handlers.push_back( &eventHandler ); return true; } return false; } bool operator-=( TEventHandler& eventHandler ) { auto it = findEventHandler( eventHandler ); if( it != m_handlers.end() ) { TEventHandler* removedEventHandler = *it; m_handlers.erase( it ); delete removedEventHandler; return true; } return false; } private: inline TEventHandlerIt findEventHandler( TEventHandler& eventHandler ) const { return std::find_if( m_handlers.cbegin(), m_handlers.cend(), [ &eventHandler ]( const TEventHandler* oneHandler ) { return ( *oneHandler == eventHandler ); } ); } std::list<TEventHandler*> m_handlers; . . . };
هنا ، تقوم وظائف الإضافة / الإزالة للمعالج بإرجاع
true في حالة النجاح
والخطأ إذا لم يتم تنفيذ الإجراء المقابل (إضافة أو إزالة).
نعم ، تتضمن حالة الاستخدام مع المقارنة إنشاء معالجات مؤقتة مضافة في أي مكان ولا يتم حذفها في أي مكان. لكن المزيد عن ذلك لاحقًا.
هل يمكن استخدام هذا؟ لم يتم تنفيذه بالكامل بعد.
إزالة معالج داخل معالج
لذلك ، نواجه على الفور عطلًا أثناء تنفيذ التعليمات البرمجية ، حيث يقوم المعالج بإلغاء اشتراك نفسه من الحدث (أعتقد أنه ليس من
حالات الاستخدام النادرة عندما يتم قطع المعالج ذاتيًا تحت أي ظروف):
class TestWindow { . . . public: TEvent<const std::string&, unsigned int> onButtonClick; static TestWindow& instance(); . . . }; class ClickEventHandler { . . . public: void testWindowButtonClick( const std::string&, unsigned int ) { TestWindow::instance().onButtonClick -= MY_METHOD_HANDLER( ClickEventHandler::testWindowButtonClick ); } . . . }; int main( int argc, char *argv[] ) { . . . ClickEventHandler clickEventHandler; TestWindow::instance().onButtonClick += METHOD_HANDLER( clickEventHandler, ClickEventHandler::testWindowButtonClick ); . . . }
تنشأ المشكلة لسبب بسيط للغاية:
- يتم تشغيل الحدث ويبدأ في تكرار معالجات (باستخدام عوامل التكرار) ، ويطلق عليها ؛
- المعالج التالي نفسه يؤدي إلى حذف نفسه ؛
- يقوم الحدث بحذف المعالج المحدد ، مما يجعل المكرر المناظر غير صالح ؛
- بعد الانتهاء من هذا المعالج ، يعود الحدث إلى تعداد الآخرين ، ومع ذلك ، فإن المكرر الحالي (المقابل للمعالج البعيد) غير صالح بالفعل ؛
- يحاول الحدث الوصول إلى المكرر غير الصالح ، مما يتسبب في السقوط.
لذلك ، من الضروري التحقق من الحالات التي يمكن فيها تغيير قائمة المعالجات ، مما يؤدي إلى إبطال التكرار ؛ ثم تنفيذ حماية القراءة لمثل هذه التكرارات.
ميزة
std :: list 'في هذا التطبيق هي حقيقة أنه عند حذفه يجعل مكرر واحد فقط غير صالح - على العنصر المحذوف (يؤثر ، على سبيل المثال ، ما يلي) ؛ وإضافة عنصر لا يؤدي إلى إبطال أي تكرارات على الإطلاق. وبالتالي ، نحتاج إلى التحكم في الحالة الوحيدة: حذف عنصر يكون مكرره الحالي في التعداد الحالي للعناصر. في هذه الحالة ، يمكنك ، على سبيل المثال ، عدم حذف عنصر ، ولكن ببساطة وضع علامة على أن العنصر الحالي سيتم حذفه ، وتركه يتم داخل تعداد العناصر.
سيكون من الممكن البدء في تنفيذ هذا على الفور ، لكني أقترح حل هذه المشكلة مع ما يلي.
سلامة الخيط
من المحتمل أن تكون الاستدعاءات لثلاث وظائف محتملة - إضافة معالجات وحذفها وفرزها (عند تشغيل حدث) - ممكنة من سلاسل المحادثات المختلفة في أوقات عشوائية. وهذا يخلق مجالًا كاملاً من الاحتمالات لـ "التقاطع" في الوقت المناسب ، و "التداخل" في تنفيذها على بعضها البعض وسقوط البرنامج نتيجة لذلك. دعونا نحاول تجنب هذا.
كائنات المزامنة هي كل شيء لدينا .
template<class ...TParams> class TEvent { using TEventHandler = AbstractEventHandler<TParams...>; using TEventHandlerIt = typename std::list<TEventHandler*>::const_iterator; public: TEvent() : m_handlers(), m_currentIt(), m_isCurrentItRemoved( false ), m_handlerListMutex() { } void operator()( TParams... params ) { m_handlerListMutex.lock_shared(); m_isCurrentItRemoved = false; m_currentIt = m_handlers.begin(); while( m_currentIt != m_handlers.end() ) { m_handlerListMutex.unlock_shared(); ( *m_currentIt )->call( params... ); m_handlerListMutex.lock_shared(); if( m_isCurrentItRemoved ) { m_isCurrentItRemoved = false; TEventHandlerIt removedIt = m_currentIt; ++m_currentIt; deleteHandler( removedIt ); } else { ++m_currentIt; } } m_handlerListMutex.unlock_shared(); } bool operator+=( TEventHandler& eventHandler ) { std::unique_lock<std::shared_mutex> _handlerListMutexLock( m_handlerListMutex ); if( findEventHandler( eventHandler ) == m_handlers.end() ) { m_handlers.push_back( std::move( eventHandler ) ); return true; } return false; } bool operator-=( TEventHandler& eventHandler ) { std::unique_lock<std::shared_mutex> _handlerListMutexLock( m_handlerListMutex ); auto it = findEventHandler( eventHandler ); if( it != m_handlers.end() ) { if( it == m_currentIt ) m_isCurrentItRemoved = true; else deleteHandler( it ); return true; } return false; } private:
لا تنس ترك النافذة مفتوحة تقريبًا في كل مرة تتصل فيها بكل معالج. يعد ذلك ضروريًا بحيث يمكن داخل المعالج الوصول إلى الحدث وتغييره (على سبيل المثال ، إضافة / إزالة معالجات) دون التسبب في حالة
توقف تام . لا يمكنك أن تخاف من صحة البيانات ، لأنه ، كما اكتشفنا ، الشيء الوحيد الذي يؤدي إلى ذلك هو حذف العنصر الحالي ، وقد تمت معالجة هذا الموقف.
UPD1. شكرا
الغشاش ،
اقترح أن
std :: Shared_mutex يظهر فقط في
C ++ 17 (و
std :: shared_lock فقط في
C ++ 14 ). أولئك الذين يعتبر هذا أمرًا بالغ الأهمية بالنسبة لهم ربما يجب عليهم أن يفعلوا مع
std :: mutex .
UPD2. علاوة على ذلك ، حول سلامة الخيط (بدون الحفاظ على تسلسل السرد).
مشكلة رؤية الحدث
عند استخدام حدث كعضو في فصل دراسي ، يبدو من المنطقي جعله
عامًا ، بحيث يمكن لكائنات الجهات الخارجية إضافة / إزالة معالجاتها. ومع ذلك ، سيؤدي هذا إلى
عامل التشغيل () ، أي سيكون من الممكن أيضًا الوصول إلى مكالمة الحدث من الخارج ، والتي قد تكون في بعض الحالات غير مقبولة. سنحل هذه المشكلة عن طريق عزل واجهة مجردة من فئة الحدث (
TEvent <...> ) مخصصة فقط للتعامل مع المعالجات.
template<class ...TParams> class IEvent { protected: using TEventHandler = AbstractEventHandler<TParams...>; public: bool operator+=( TEventHandler& eventHandler ) { return addHandler( eventHandler ); } bool operator-=( TEventHandler& eventHandler ) { return removeHandler( eventHandler ); } protected: IEvent() {} virtual bool addHandler( TEventHandler& eventHandler ) = 0; virtual bool removeHandler( TEventHandler& eventHandler ) = 0; };
template<class ...TParams> class TEvent : public IEvent<TParams...> { . . . public: TEvent() : IEvent<TParams...>() . . . { } protected: virtual bool addHandler( TEventHandler& eventHandler ) override {
الآن يمكننا تقسيم إلى نطاقات مختلفة جزء الحدث المسؤول عن العمل مع معالجات ، والجزء المسؤول عن استدعاء ذلك.
class TestWindow { . . . public: TestWindow() : onButtonClick( m_onButtonClick ), m_onButtonClick() { } IEvent<const std::string&, unsigned int>& onButtonClick; protected: TEvent<const std::string&, unsigned int> m_onButtonClick; . . . };
وبالتالي ، يمكن الآن لكائنات / جهات خارجية إضافة / إزالة معالجاتها عبر
TestWindow :: onButtonClick ، ومع ذلك لن تتمكن من تشغيل هذا الحدث بنفسها. يمكن إجراء مكالمة الآن فقط داخل فئة
TestWindow (ونسلها ، إذا كان نطاق الحدث ، كمثال ،
محميًا ).
بدأ الكود التافه يتحول ببطء إلى شيء بشع ، لكن هذه ليست النهاية.
تطابق معلمات الأحداث ومعالجاتها
في التطبيق الحالي ، يجب أن يكون للحدث وأي من معالجاته قائمة معلمات مطابقة تمامًا. هذا يؤدي إلى عدد من العيوب.
الأول. افترض أن لدينا قالب فئة يوجد فيه حدث بمعلمة قالب.
template<class TSource> class MyClass { . . . public: TEvent<const TSource&> onValueChanged; . . . };
نظرًا لأن النوع الذي سيتم استخدامه هنا غير معروف مسبقًا ، فمن المنطقي أن يتم تمريره عن طريق رابط ثابت ، وليس حسب القيمة. ومع ذلك ، الآن بالنسبة لأي تنفيذ ، حتى مع الأنواع الأساسية ، يجب أن تكون المعالجات المقابلة موجودة.
MyClass<bool> myBoolClass; . . . template<class TSource> class MyHandlerClass { . . . private: void handleValueChanged1( const bool& newValue ); void handleValueChanged2( bool newValue ); . . . }; . . . MyHandlerClass myHandlerClass; myBoolClass.onValueChanged += METHOD_HANDLER( myHandlerClass, MyHandlerClass::handleValueChanged1 );
أود أن أكون قادرًا على توصيل معالجات النموذج
MyHandlerClass :: handleValueChanged2 بحدث مشابه ، ولكن حتى الآن لا يوجد مثل هذا الاحتمال.
الثاني. دعنا نحاول تنفيذ معالج مرح مشابه لطريقة معالج موجودة (-وظيفة-عضو في فئة).
template<class TFunctor, class ...TParams> class FunctorEventHandler : public AbstractEventHandler<TParams...> { public: FunctorEventHandler( TFunctor& functor ) : AbstractEventHandler<TParams...>(), m_functor( functor ) { } virtual void call( TParams... params ) override final { m_functor( params... ); } private: TFunctor& m_functor; }; template<class TFunctor, class ...TParams> AbstractEventHandler<TParams...>& createFunctorEventHandler( TFunctor&& functor ) { return *new FunctorEventHandler<TFunctor, TParams...>( functor ); } #define FUNCTOR_HANDLER( Functor ) createFunctorEventHandler( Functor )
حاول الآن أن تفسد الأمر على حدث ما.
class TestWindow { . . . public: TEvent<const std::string&, unsigned int> onButtonClick; . . . }; struct ClickEventHandler { void operator()( const std::string&, unsigned int ) { . . . } }; int main( int argc, char *argv[] ) { . . . TestWindow testWindow; ClickEventHandler clickEventHandler; testWindow.onButtonClick += FUNCTOR_HANDLER( clickEventHandler ); . . . }
ستكون النتيجة خطأ في الترجمة. بالنسبة للدالة
createFunctorEventHandler ، لا يستطيع المحول البرمجي استنتاج أنواع
TParams ... من الوسيطة الوحيدة لهذه الوظيفة - الممول نفسه. لا يحتوي الممر على أي معلومات حول نوع المعالج الذي سيتم إنشاؤه بناءً عليه. الشيء الوحيد الذي يمكن القيام به في هذه الحالة هو كتابة شيء مثل:
testWindow.onButtonClick += createFunctorEventHandler<ClickEventHandler, const std::string&, unsigned int>( clickEventHandler );
لكنك لا تريد القيام بذلك على الإطلاق.
ربط حدث بأنواع مختلفة من المعالجات
لذا ، هناك قائمة الرغبات ، والأمر متروك للتنفيذ. سننظر في الموقف باستخدام مثال معالج الممر ؛ سيتم الحصول على طريقة معالج (-الوظيفة-عضو في فئة) بطريقة مماثلة.
نظرًا لأنه على أساس المعلق فقط ، من المستحيل تحديد قائمة معلمات المعالج المقابل ، فلن نقوم بذلك. يصبح هذا السؤال ذا صلة ليس في وقت إنشاء المعالج ، ولكن في وقت محاولة إرفاقه بحدث معين. ونعم ، هاتان نقطتان مختلفتان. يمكن تنفيذ هذه الفكرة على النحو التالي:
template<class TFunctor> class FunctorHolder; template<class TFunctor, class ...TParams> class FunctorEventHandler : public AbstractEventHandler<TParams...> { public: FunctorEventHandler( FunctorHolder<TFunctor>& functorHolder ) : AbstractEventHandler<TParams...>(), m_functorHolder( functorHolder ) { } virtual void call( TParams... params ) override { m_functorHolder.m_functor( params... ); } private: FunctorHolder<TFunctor>& m_functorHolder; . . . };
template<class TFunctor> class FunctorHolder { public: FunctorHolder( TFunctor& functor ) : m_functor( functor ) { } template<class ...TCallParams> operator AbstractEventHandler<TCallParams...>&() { return *new FunctorEventHandler<TFunctor, TCallParams...>( *this ); } private: TFunctor& m_functor; . . . template<class TFunctor, class ...TParams> friend class FunctorEventHandler; };
template<class TFunctor> FunctorHolder<TFunctor>& createFunctorEventHandler( TFunctor&& functor ) { return *new FunctorHolder<TFunctor>( functor ); } #define FUNCTOR_HANDLER( Functor ) createFunctorEventHandler( Functor ) #define LAMBDA_HANDLER( Lambda ) FUNCTOR_HANDLER( Lambda ) #define STD_FUNCTION_HANDLER( StdFunction ) FUNCTOR_HANDLER( StdFunction ) #define FUNCTION_HANDLER( Function ) FUNCTOR_HANDLER( &Function )
template<class ...TParams> class IEvent { protected: using TEventHandler = AbstractEventHandler<TParams...>; public: template<class TSome> bool operator+=( TSome&& some ) { return addHandler( static_cast<TEventHandler&>( some ) ); } template<class TSome> bool operator-=( TSome&& some ) { return removeHandler( static_cast<TEventHandler&>( some ) ); } protected: IEvent() {} virtual bool addHandler( TEventHandler& eventHandler ) = 0; virtual bool removeHandler( TEventHandler& eventHandler ) = 0; };
باختصار ، يكون فصل لحظات إنشاء المعالج وملاحقته بالحدث هنا أكثر وضوحًا من ذي قبل. هذا يتحايل على المشاكل الموصوفة في الفقرة السابقة. سيحدث اختبار توافق النوع عند محاولة إرفاق
FunctorHolder معينة إلى
FunctorEventHandler ، أو بالأحرى ، إنشاء مثيل
لفئة FunctorEventHandler <...> بنوع موصِّل محدد للغاية ؛ وفي هذا الفصل سيكون هناك سطر من الكود
m_functorHolder.m_functor (المعلمات ...) ؛ ، والتي لا يمكن تجميعها ببساطة لمجموعة من الأنواع غير المتوافقة مع المعلق (أو إذا لم يكن ممرًا على الإطلاق ، أي كائن لا يحتوي على
عامل تشغيل () ).
أكرر أن مشكلة حذف الأشياء المؤقتة سيتم مناقشتها أدناه. بالإضافة إلى ذلك ، تجدر الإشارة إلى أنه تم إنشاء مجموعة من وحدات الماكرو لكل حالة ، أولاً ، من أجل توضيح إمكانات هذا النوع من المعالجات ، وثانيًا ، في حالة تعديل محتمل لأي منها مع ملف.
تحقق من النتيجة.
class TestWindow { . . . public: TEvent<const std::string&, unsigned int> onButtonClick; . . . }; struct Functor { void operator()( const std::string&, unsigned int ) {} }; struct Functor2 { void operator()( std::string, unsigned int ) {} }; struct Functor3 { void operator()( const std::string&, const unsigned int& ) {} }; struct Functor4 { void operator()( std::string, const unsigned int& ) {} }; struct Functor5 { void operator()( std::string&, unsigned int& ) {} }; struct Functor6 { void operator()( const std::string&, unsigned int& ) {} }; struct Functor7 { void operator()( std::string&, const unsigned int& ) {} }; int main( int argc, char *argv[] ) { . . . TestWindow testWindow; Functor functor; Functor2 functor2; Functor3 functor3; Functor4 functor4; Functor5 functor5; Functor6 functor6; Functor7 functor7; testWindow.onButtonClick += FUNCTOR_HANDLER( functor );
يحدث خطأ في الترجمة عند محاولة تحويل إحدى المعلمات من
const lvalue إلى
lvalue . لا يؤدي التحويل من
rvalue إلى
lvalue غير
المألوف إلى حدوث خطأ ، على الرغم من أنه تجدر الإشارة إلى أنه يخلق تهديدًا محتملاً
للتصوير الذاتي في الساق: سيكون المعالج قادرًا على تغيير المتغير المنسوخ إلى المكدس ، والذي سيتم حذفه بفرح عند خروج هذا المعالج.
بشكل عام ، يجب أن تظهر رسالة الخطأ على النحو التالي:
Error C2664 'void Functor5::operator ()(std::string &,unsigned int &)': cannot convert argument 1 from 'const std::string' to 'std::string &'
لمزيد من الوضوح ، عند استخدام الأحداث والمعالجات في رمز جهة خارجية ، يمكنك إضافة رسالة الخطأ الخاصة بك. سيتطلب ذلك كتابة هيكل داعم صغير (أعترف ، لقد تجسست على نهج مماثل في مكان ما):
namespace { template<class TFunctor, class ...TParams> struct IsFunctorParamsCompatible { private: template<class TCheckedFunctor, class ...TCheckedParams> static constexpr std::true_type exists( decltype( std::declval<TCheckedFunctor>()( std::declval<TCheckedParams>()... ) )* = nullptr ); template<class TCheckedFunctor, class ...TCheckedParams> static constexpr std::false_type exists( ... ); public: static constexpr bool value = decltype( exists<TFunctor, TParams...>( nullptr ) )::value; }; }
template<class TFunctor, class ...TParams> class FunctorEventHandler : public AbstractEventHandler<TParams...> { . . . public: virtual void call( TParams... params ) override { static_assert( IsFunctorParamsCompatible<TFunctor, TParams...>::value, "Event and functor arguments are not compatible" ); m_functorHolder->m_functor( params... ); } . . . };
يعتمد هذا العمل على آلية
SFINAE . باختصار ، هناك محاولة لتجميع الوظيفة الأولى
موجودة ، ومع ذلك ، إذا لم يفلح ذلك بسبب عدم توافق الحجج (أو غياب
العامل () من ما تم تمريره كممرح) ، فإن المترجم لا يلقي خطأ ، ولكنه يحاول ببساطة تجميع الوظيفة الثانية ؛ نحن نفعل كل شيء حتى ينجح تجميعه دائمًا ، وبعد ذلك ، بناءً على حقيقة الوظيفة التي تم تجميعها ، نستنتج (بكتابة النتيجة إلى
القيمة ) حول توافق الحجج لأنواع معينة.
الآن ستظهر رسالة الخطأ على النحو التالي:
Error C2338 Event and functor arguments are not compatible Error C2664 'void Functor5::operator ()(std::string &,unsigned int &)': cannot convert argument 1 from 'const std::string' to 'std::string &'
بالإضافة إلى رسالة خطأ إضافية أكثر
إفادة ، يحل هذا النهج مشكلة تحويل الوسيطة (s) من
rvalue إلى
unvst lvalue : الآن يتسبب في خطأ عدم توافق الوسيطة ، أي
تؤدي محاولة إضافة معالج
functor6 من المثال أعلاه إلى حدوث خطأ في وقت الترجمة.
UPD صقل (بدون الحفاظ على تسلسل السرد).
مقارنة Functor
نظرًا للتغييرات في فئة المعالج ، سيتغير تطبيق مقارنة حالات هذا الفصل قليلاً. مرة أخرى ، سأقدم تطبيقًا فقط على معالج المواهب ، لأن طريقة المعالج (-الوظيفة-عضو في الفصل) ستبدو متشابهة.
template<class ...TParams> class AbstractEventHandler { . . . using MyType = AbstractEventHandler<TParams...>; public: bool operator==( const MyType& other ) const { return isEquals( other ); } bool operator!=( const MyType& other ) const { return !( *this == other ); } protected: virtual bool isEquals( const MyType& other ) const = 0; . . . };
template<class TFunctor, class ...TParams> class FunctorEventHandler : public AbstractEventHandler<TParams...> { . . . using MyType = FunctorEventHandler<TFunctor, TParams...>; protected: virtual bool isEquals( const AbstractEventHandler<TParams...>& other ) const override { const MyType* _other = dynamic_cast<const MyType*>( &other ); return ( _other != nullptr && *m_functorHolder == *_other->m_functorHolder ); } private: FunctorHolder<TFunctor>& m_functorHolder; . . . };
template<class TFunctor> class FunctorHolder { . . . using MyType = FunctorHolder<TFunctor>; public: bool operator==( const MyType& other ) const { return ( m_functor == other.m_functor ); } bool operator!=( const MyType& other ) const { return !( *this == other ); } private: TFunctor& m_functor; . . . };
على هذا ، فإن أوجه التشابه في تنفيذ نهاية المقارنة والجزء يبدأ فقط لمعالجات المعلق.
كما هو مذكور أعلاه ، حصلنا على عدة أنواع من معالجات المعلق: كائنات المعلق مباشرة ، وتعبيرات لامدا ، وحالات فئة
std :: function ، والوظائف الفردية. من بين هذه الأشياء ، لا يمكن مقارنة كائنات الدوافع ، وتعبيرات لامدا ، وحالات فئة
std :: function باستخدام
عامل التشغيل == (يجب أن تتم مقارنتها في العنوان) ، ولكن يمكن للوظائف الفردية ، لأن مخزنة بالفعل في. من أجل عدم إعادة كتابة وظيفة المقارنة بشكل منفصل لكل حالة ، نكتبها في شكل عام:
namespace { template<class TEqu, class TEnabled = void> struct EqualityChecker; template<class TEquatable> struct EqualityChecker<TEquatable, typename std::enable_if<is_equatable<TEquatable>::value>::type> { static constexpr bool isEquals( const TEquatable& operand1, const TEquatable& operand2 ) { return ( operand1 == operand2 ); } }; template<class TNonEquatable> struct EqualityChecker<TNonEquatable, typename std::enable_if<!is_equatable<TNonEquatable>::value>::type> { static constexpr bool isEquals( const TNonEquatable& operand1, const TNonEquatable& operand2 ) { return ( &operand1 == &operand2 ); } }; }
من المفهوم أن is_equatable هو قالب مساعد يحدد ما إذا كان يمكن التحقق من حالتين من نوع معين من أجل المساواة. بمساعدتها ، باستخدام std :: enable_if ، نختار أحد بنيتي EqualityChecker المتخصصة جزئيًا ، والتي ستجري مقارنة: حسب القيمة أو حسب العنوان. يتم تنفيذ Is_equatable ، ويمكن أن يكون على النحو التالي: template<class T> class is_equatable { private: template<class U> static constexpr std::true_type exists( decltype( std::declval<U>() == std::declval<U>() )* = nullptr ); template<class U> static constexpr std::false_type exists( ... ); public: static constexpr bool value = decltype( exists<T>( nullptr ) )::value; };
يعتمد هذا التنفيذ على آلية SFINAE ، التي تم استخدامها بالفعل من قبل . هنا فقط نتحقق من وجود عامل == لحالات فئة معينة.وبهذه الطريقة البسيطة ، يكون تنفيذ مقارنة المعالجين - المربين جاهزين.جمع القمامة
كن متساهلاً ، أردت أيضًا إدراج عنوان مرتفع.نحن نقترب من النهاية ، وحان الوقت للتخلص من العدد الهائل من الأشياء التي تم إنشاؤها والتي لا يتحكم بها أحد.ينشئ كل إجراء حدث باستخدام معالج كائنين: الحامل ، الذي يخزن الجزء القابل للتنفيذ من المعالج ، و EventHandlerربطه بالحدث. لن ننسى أنه في حالة محاولة إعادة إضافة المعالج ، لن تحدث أي إضافة - يتم تعليق شيئين في الهواء (ما لم يتم ، بالطبع ، فحص هذه الحالة بشكل منفصل في كل مرة). حالة أخرى: إزالة المعالج ؛ يتم إنشاء كائنين جديدين أيضًا للبحث عن نفس (متساوٍ) في قائمة معالجات الأحداث ؛ بالطبع ، تم حذف المعالج الموجود من القائمة (إن وجد) ، وهذا المعالج المؤقت الذي تم إنشاؤه للبحث ويتكون من شيئين ، هو مرة أخرى "في الهواء". بشكل عام ، ليس باردا.أنتقل إلى المؤشرات الذكية . نحتاج إلى تحديد دلالات ملكية كل من كائني المعالج: الملكية الوحيدة ( std :: unique_ptr ) أو المشتركة ( std :: shared_ptr ).حامل، إلى جانب استخدام الحدث نفسه عند تخزينه أو إزالته ، يجب تخزينه في EventHandler ، لذلك نستخدمه للملكية المشتركة ، وبالنسبة لـ EventHandler فهو وحيد ، لأنه بعد الإنشاء ، سيتم تخزينه فقط في قائمة معالجات الأحداث.نحن ندرك هذه الفكرة: template<class ...TParams> class AbstractEventHandler { . . . public: virtual ~AbstractEventHandler() {} . . . }; template<class ...Types> using THandlerPtr = std::unique_ptr<AbstractEventHandler<Types...>>;
namespace { template<class TSome> struct HandlerCast { template<class ...Types> static constexpr THandlerPtr<Types...> cast( TSome& some ) { return static_cast<THandlerPtr<Types...>>( some ); } }; template<class TPtr> struct HandlerCast<std::shared_ptr<TPtr>> { template<class ...Types> static constexpr THandlerPtr<Types...> cast( std::shared_ptr<TPtr> some ) { return HandlerCast<TPtr>::cast<Types...>( *some ); } }; }
template<class TMethodHolder, class ...TParams> class MethodEventHandler : public AbstractEventHandler<TParams...> { . . . using TMethodHolderPtr = std::shared_ptr<TMethodHolder>; public: MethodEventHandler( TMethodHolderPtr methodHolder ) : AbstractEventHandler<TParams...>(), m_methodHolder( methodHolder ) { assert( m_methodHolder != nullptr ); } private: TMethodHolderPtr m_methodHolder; . . . }; template<class TObject, class ...TParams> class MethodHolder { using MyType = MethodHolder<TObject, TParams...>; using TMethod = void( TObject::* )( TParams... ); public: MethodHolder( TObject& object, TMethod method ) { . . . } template<class ...TCallParams> operator THandlerPtr<TCallParams...>() { return THandlerPtr<TCallParams...>( new MethodEventHandler<MyType, TCallParams...>( ) ); } . . . }; template<class TObject, class ...TParams> std::shared_ptr<MethodHolder<TObject, TParams...>> createMethodEventHandler( TObject& object, void( TObject::*method )( TParams... ) ) { return std::shared_ptr<MethodHolder<TObject, TParams...>>( new MethodHolder<TObject, TParams...>( object, method ) ); } #define METHOD_HANDLER( Object, Method ) createMethodEventHandler( Object, &Method ) #define MY_METHOD_HANDLER( Method ) METHOD_HANDLER( *this, Method )
الأشياء الأولى أولاً.للبدء ، الحدث وواجهة العمل مع المعالجات. في الأخير ، لم يعد بإمكانك تحويل الأنواع مباشرة باستخدام static_cast ، لأن النوع الذي يتم تحويله يكمن "في الداخل" std :: Shared_ptr . الآن ، لمثل هذا التحول ، سنستخدم بنية HandlerCast الإضافية ، والتي ، من خلال تخصصها الخاص ، ستوفر الوصول إلى الكائن داخل std :: Shared_ptr ، وتعمل بالفعل معه (في تنفيذه غير المتخصص) ، ستطبق البث القديم الجيد .الحدث نفسه ؛ هناك العديد من التغييرات المهمة هنا أيضًا. أولاً ، سنتوقف يدويًا عن حذف مثيلات المعالج في المدمر وعند الحذف ؛ يكفي الآن إزالة المؤشر الذكي باستخدام هذا المعالج من القائمة. بالإضافة إلى ذلك ، عند إضافة معالج ، من المهم ألا ننسى std :: move ، لأن لا يدعم std :: unique_ptr النسخ (وهو أمر منطقي تمامًا لمثل هذه الدلالات).دعنا ننتقل إلى معالجات. وفقا للتقاليد القديمة ، يتم إعطاء واحد منهم فقط ، والثاني مشابه. وهنا ، للوهلة الأولى ، يتعلق الأمر كله بتغيير أنواع الكائنات المخزنة / التي تم إنشاؤها من الروابط / المؤشرات إلى المؤشرات الذكية.ولكن هناك نقطة خفية واحدة. وظيفة createMethodEventHandler عودة STD :: shared_ptr إلى مثيلMethodHolder . بعد ذلك بقليل ، ستتم محاولة تحويله إلى نوع معالج ( MethodEventHandler ) ، حيث سيتعين عليه إنشاء مثيل جديد لـ MethodEventHandler ، ثم تمريره إلى مُنشئ std :: Shared_ptr على نفسه. هذا هو بالضبط ما كان مخصصًا لحذف مثيل MethodHolder لاحقًا عند حذف مثيل MethodEventHandler . ولكن المشكلة هي أن MethodHolder ليس لديه حق الوصول إلى std :: Shared_ptr الذي تم إنشاؤه بالفعل والذي يقوم بتخزينه بنفسه.لحل المشكلة ، عليك تخزين مؤشر ذكي لنفسك في MethodHolder . ومع ذلك ، حتى لا يؤثر على إزالته ، نستخدمهالأمراض المنقولة جنسيا ::: الضعيفة : template<class TObject, class ...TParams> class MethodHolder { using MyType = MethodHolder<TObject, TParams...>; using TMethod = void( TObject::* )( TParams... ); public: template<class ...TCallParams> operator THandlerPtr<TCallParams...>() { return THandlerPtr<TCallParams...>( new MethodEventHandler<MyType, TCallParams...>( m_me.lock() ) ); } template<class TObject, class ...TParams> static std::shared_ptr<MyType> create( TObject& object, TMethod method ) { std::shared_ptr<MyType> result( new MyType( object, method ) ); result->m_me = result; return result; } private: MethodHolder( TObject& object, TMethod method ) : m_object( object ), m_method( method ) { assert( m_method != nullptr ); } TObject& m_object; TMethod m_method; std::weak_ptr<MyType> m_me; }; template<class TObject, class ...TParams> std::shared_ptr<MethodHolder<TObject, TParams...>> createMethodEventHandler( TObject& object, void( TObject::*method )( TParams... ) ) { return MethodHolder<TObject, TParams...>::create( object, method ); }
للتوضيح ، سأعطي ترتيبًا تقريبيًا للأحداث عند إزالة معالج من حدث (أعتذر عن تورية عشوائية):- يزيل الحدث العنصر من القائمة ( m_handlers.erase (it)؛ ) ، مما يؤدي إلى استدعاء المدمر الخاص به ؛
- يسمى المدمر std :: unique_ptr ، مما يؤدي إلى استدعاء المدمر للكائن المُدار ؛
- ويسمى المدمر MethodEventHandler ، الذي يزيل كافة الحقول كائن، بما في ذلك حقل m_methodHolder ، وهو من الأمراض المنقولة جنسيا :: shared_ptr .
- std::shared_ptr ; , (.. ) ( MethodHolder ); , std::weak_ptr ;
- MethodHolder , , , m_me , std::weak_ptr ;
- std::weak_ptr ; ; .. std::weak_ptr , ;
- .
من المهم أن نتذكر أن المدمر لفئة AbstractEventHandler يجب أن يكون ظاهريًا ؛ خلاف ذلك ، بعد البند 2 في البند 3 ، سيتم استدعاء المدمر AbstractEventHandler ولن يتم تنفيذ إجراءات أخرى.اتصال الحدث والمعالج
في بعض الحالات ، عند إضافة / إزالة معالج واحد من حدث ما في كثير من الأحيان (وفقًا لبعض المنطق) ، لا ترغب في الإزعاج ، والحصول على نسخة من الحدث ومثيل المعالج في كل مرة ، لتنفيذ اشتراك / إلغاء اشتراك مرة أخرى من هذا الحدث. ولكن أريد توصيلها مرة واحدة ، ثم ، عند الضرورة ، العمل مع هذا الاتصال ، بإضافة / إزالة معه معالج محدد مسبقًا من حدث محدد مسبقًا. يمكنك تنفيذ ذلك على النحو التالي: template<class ...Types> using THandlerPtr = std::shared_ptr<AbstractEventHandler<Types...>>;
template<class ...TParams> class IEvent { . . . protected: using TEventHandlerPtr = THandlerPtr<TParams...>; virtual bool isHandlerAdded( const TEventHandlerPtr& eventHandler ) const = 0; virtual bool addHandler( TEventHandlerPtr eventHandler ) = 0; virtual bool removeHandler( TEventHandlerPtr eventHandler ) = 0; friend class HandlerEventJoin<TParams...>; . . . }; template<class ...TParams> class TEvent : public IEvent<TParams...> { . . . protected: virtual bool isHandlerAdded( const TEventHandlerPtr& eventHandler ) const override { std::shared_lock<std::shared_mutex> _handlerListMutexLock( m_handlerListMutex ); return ( findEventHandler( eventHandler ) != m_handlers.end() ); } virtual bool addHandler( TEventHandlerPtr eventHandler ) override { . . . } virtual bool removeHandler( TEventHandlerPtr eventHandler ) override { . . . } private:
template<class ...TParams> class HandlerEventJoin { public: HandlerEventJoin( IEvent<TParams...>& _event, THandlerPtr<TParams...> handler ) : m_event( _event ), m_handler( handler ) { } inline bool isJoined() const { return m_event.isHandlerAdded( m_handler ); } inline bool join() { return m_event.addHandler( m_handler ); } inline bool unjoin() { return m_event.removeHandler( m_handler ); } private: IEvent<TParams...>& m_event; THandlerPtr<TParams...> m_handler; };
كما ترى ، تمت إضافة مكان آخر محتمل لتخزين مثيل المعالج ، لذلك سنستخدم std :: Shared_ptr بدلاً من std :: unique_ptr لهذا الغرض .ومع ذلك ، فإن هذا الفصل ، بالنسبة لي ، غير مريح قليلاً للاستخدام. أود تخزين وإنشاء نسخ اتصال بدون قائمة من المعلمات التي تنسخ قالب الصف.ننفذ هذا باستخدام فئة السلف المجردة والمغلف: class AbstractEventJoin { public: virtual ~AbstractEventJoin() {} virtual bool isJoined() const = 0; virtual bool join() = 0; virtual bool unjoin() = 0; protected: AbstractEventJoin() {} };
template<class ...TParams> class HandlerEventJoin : public AbstractEventJoin { . . . public: virtual inline bool isJoined() const override { . . . } virtual inline bool join() override { . . . } virtual inline bool unjoin() override { . . . } . . . };
class EventJoinWrapper { public: template<class TSome, class ...TParams> inline EventJoinWrapper( IEvent<TParams...>& _event, TSome&& handler ) : m_eventJoin( std::make_shared<HandlerEventJoin<TParams...>>( _event, HandlerCast<TSome>::cast<TParams...>( handler ) ) ) { } constexpr EventJoinWrapper() : m_eventJoin( nullptr ) { } ~EventJoinWrapper() { if( m_eventJoin != nullptr ) delete m_eventJoin; } operator bool() const { return isJoined(); } bool isAssigned() const { return ( m_eventJoin != nullptr ); } bool isJoined() const { return ( m_eventJoin != nullptr && m_eventJoin->isJoined() ); } bool join() { return ( m_eventJoin != nullptr ? m_eventJoin->join() : false ); } bool unjoin() { return ( m_eventJoin != nullptr ? m_eventJoin->unjoin() : false ); } private: AbstractEventJoin* m_eventJoin; }; using EventJoin = EventJoinWrapper;
HandlerCast هو نفس الهيكل الداعم الذي تم استخدامه هنا . بالمناسبة ، من المهم عدم نسيان جعل أداة AbstractEventJoin المدمرة افتراضية بحيث عندما تحذف مثيلها في أداة EventJoinWrapper المدمرة ، يتم استدعاء أداة إتلاف HandlerEventJoin ، وإلا فإن حقل THandlerPtr وبالتالي ، لن يتم إتلاف المعالج نفسه.يبدو أن هذا التطبيق قابل للتنفيذ ، ولكن للوهلة الأولى فقط. سيؤدي نسخ أو نقل مثيل EventJoinWrapper إلى حذف m_eventJoin في المدمر الخاص به مرة أخرى . لذلك ، نستخدم std :: Shared_ptr لتخزين المثيلAbstractEventJoin ، بالإضافة إلى تنفيذ دلالات محسنة قليلاً للحركة (والنسخ) ، لأن ستكون هذه عملية متكررة محتملة. class EventJoinWrapper { public: EventJoinWrapper( EventJoinWrapper&& other ) : m_eventJoin( std::move( other.m_eventJoin ) ) { } EventJoinWrapper( EventJoinWrapper& other ) : m_eventJoin( other.m_eventJoin ) { } ~EventJoinWrapper() { } EventJoinWrapper& operator=( EventJoinWrapper&& other ) { m_eventJoin = std::move( other.m_eventJoin ); return *this; } EventJoinWrapper& operator=( const EventJoinWrapper& other ) { m_eventJoin = other.m_eventJoin; return *this; } . . . private: std::shared_ptr<AbstractEventJoin> m_eventJoin; };
الآن عند توصيل معالج بحدث ، يمكنك على الفور إرجاع مثيل اتصال جديد: template<class ...TParams> class IEvent { . . . public: template<class TSome> EventJoin operator+=( TSome&& some ) { EventJoin result( *this, std::forward<TSome>( some ) ); result.join(); return result; } . . . };
وبعد حل التبعية الثلاثية من خلال تضمين (IEvent <= EventJointWrapper.hpp؛ EventJointWrapper <= HandlerEventJoin.hpp؛ HandlerEventJoin <= IEvent.hpp) عن طريق تقسيم بعض الملفات إلى .h و .hpp يمكنك حتى العمل مع هذا.يتبع إنشاء مثيلات الاتصال نفس القواعد التي تعمل عند اشتراك معالج الأحداث: struct EventHolder { TEvent<const std::string&> onEvent; }; struct MethodsHolder { void method1( const std::string& ) {} void method2( std::string ) {} void method3( std::string&& ) {} void method4( std::string& ) {} void method5( const int& ) {} }; int main( int argc, char* argv[] ) { EventHolder _eventHolder; MethodsHolder _methodsHolder; EventJoin join1 = EventJoin( _eventHolder.onEvent, METHOD_HANDLER( _methodsHolder, MethodsHolder::method1 ) );
بالإضافة إلى ذلك ، يمكنك "تمكين" / "تعطيل" معالجة الأحداث (التي تم إنشاء الاتصالات من حيث المبدأ): struct EventHolder { TEvent<const std::string&, unsigned int> onEvent; }; struct MethodsHolder { void handleEvent( const std::string& text, unsigned int count ) { std::cout << "Text '" << text << "' handled " << count << " times." << std::endl; } }; int main__( int argc, char* argv[] ) { EventHolder _eventHolder; MethodsHolder methodsHolder; EventJoin eventJoin = EventJoin( _eventHolder.onEvent, METHOD_HANDLER( methodsHolder, MethodsHolder::handleEvent ) ); static const std::string handlingText = "testing..."; for( int i = 0; i < 10; ++i ) { if( eventJoin.isJoined() ) eventJoin.unjoin(); else eventJoin.join(); _eventHolder.onEvent( handlingText, i ); } return 0; }
Text 'testing...' handled 0 times. Text 'testing...' handled 2 times. Text 'testing...' handled 4 times. Text 'testing...' handled 6 times. Text 'testing...' handled 8 times.
الملخص
أولاً ، تجدر الإشارة إلى أن مهمة كتابة مقال قصير قدر الإمكان وفشل بإيجاز تمامًا.آمل أن يكون التنفيذ الناتج لمعالجة الأحداث فعالًا تمامًا وأن يكون مفيدًا لشخص ما على الأقل.مثال مرهق للغاية يوضح الميزات الرئيسية #include <iostream> #include <functional> #include "events/event.hpp" #include "events/handler/methodeventhandler.hpp" #include "events/handler/functoreventhandler.hpp" #include "events/join/handlereventjoin.hpp" #include "events/join/eventjoinwrapper.hpp" class Foo { public: Foo() : onMake( m_onMake ), m_onMake(), m_onMakeInner(), m_makeCount( 0 ) { m_onMakeInner += FUNCTOR_HANDLER( m_onMake ); } IEvent<unsigned int>& onMake; void make() { m_onMakeInner( m_makeCount++ ); } private: TEvent<unsigned int> m_onMake, m_onMakeInner; unsigned int m_makeCount; }; namespace instances { Foo& getFoo() { static Foo _foo; return _foo; } } // instances struct FunctorHandler { void operator()( unsigned int makeCount ); }; void functionHandler( unsigned int makeCount ); class ClassHandler { public: void handle( unsigned int makeCount ); }; namespace instances { FunctorHandler& getFunctorHandler() { static FunctorHandler _functorHandler; return _functorHandler; } std::function<void( unsigned int )>& getStdFunctionHandler() { static std::function<void( unsigned int )> _stdFunctionHandler = []( unsigned int makeCount ) { std::cout << "It's std::function handler" << std::endl; if( makeCount >= 2 ) instances::getFoo().onMake -= STD_FUNCTION_HANDLER( instances::getStdFunctionHandler() ); }; return _stdFunctionHandler; } ClassHandler& getClassHandler() { static ClassHandler _classHandler; return _classHandler; } } // instances void FunctorHandler::operator()( unsigned int makeCount ) { std::cout << "It's functor handler" << std::endl; if( makeCount >= 0 ) instances::getFoo().onMake -= FUNCTOR_HANDLER( instances::getFunctorHandler() ); } void functionHandler( unsigned int makeCount ) { std::cout << "It's function handler" << std::endl; if( makeCount >= 3 ) instances::getFoo().onMake -= FUNCTION_HANDLER( functionHandler ); } void ClassHandler::handle( unsigned int makeCount ) { std::cout << "It's method handler" << std::endl; if( makeCount >= 4 ) instances::getFoo().onMake -= MY_METHOD_HANDLER( ClassHandler::handle ); } int main( int argc, char* argv[] ) { Foo& foo = instances::getFoo(); auto lambdaHandler = []( unsigned int ) { std::cout << "It's lambda handler" << std::endl; }; foo.onMake += FUNCTOR_HANDLER( instances::getFunctorHandler() ); foo.onMake += LAMBDA_HANDLER( lambdaHandler ); EventJoin lambdaJoin = foo.onMake += LAMBDA_HANDLER( ( [ &foo, &lambdaHandler ]( unsigned int makeCount ) { if( makeCount >= 1 ) foo.onMake -= LAMBDA_HANDLER( lambdaHandler ); } ) ); foo.onMake += STD_FUNCTION_HANDLER( instances::getStdFunctionHandler() ); foo.onMake += FUNCTION_HANDLER( functionHandler ); foo.onMake += METHOD_HANDLER( instances::getClassHandler(), ClassHandler::handle ); for( int i = 0; i < 6; ++i ) { std::cout << "Make " << i << " time:" << std::endl; foo.make(); std::cout << std::endl; } lambdaJoin.unjoin(); return 0; }
الخلاصة:
Make 0 time: It's functor handler It's lambda handler It's std::function handler It's function handler It's method handler Make 1 time: It's lambda handler It's std::function handler It's function handler It's method handler Make 2 time: It's std::function handler It's function handler It's method handler Make 3 time: It's function handler It's method handler Make 4 time: It's method handler Make 5 time:
تجدر الإشارة إلى عدد من النقاط المهمة:- لم يتم تحديده بشكل منفصل ، لذلك سأذكر أن الحدث نفسه في هذا التنفيذ هو ممول ، مما يعني أنه يمكن أن يكون بمثابة معالج لحدث آخر ؛
- الآن لا يمكنك استخدام طرق ثابتة (وظائف أعضاء الفئة) كمعالجات ؛ أعتقد أنه إذا كانت هناك حاجة إلى مثل هذه الفرصة ، فليس من الصعب كتابة نوع جديد من المعالج لهذا بناءً على الأنواع الموجودة.
بالإضافة إلى ذلك ، في النسخة النهائية ، هناك بعض النقاط المحذوفة في المقالة لمزيد من الرؤية والقدرة على قراءة الكود:- يمكن أن يكون نوع القيمة المرتجعة للطريقة (وظيفة العضو في الفئة) للمعالج المقابل أيًا ، وليس بالضرورة لاغيًا (بالنسبة إلى المعالجين ، تم إجراء ذلك أيضًا) ؛
- يتم تطبيق التنفيذ بالكامل في مساحات الأسماء لسهولة الاستخدام في المشاريع (إذا كان هذا يبدو غير ضروري لشخص ما ، يمكنك دائمًا إزالته) ؛
- تمت إضافة محدد noexcept في بعض الأماكن .
لجميع أولئك الذين قرأوا هنا على الأقل قطريا ، القوس المنخفض. أرفق جميع التعليمات البرمجية ؛ يمكن أيضًا أخذه هنا (مع جميع التحسينات الأخيرة).كود كامل./events/helpers/is_equatable.hpp #pragma once #include <type_traits> template<class T> class is_equatable { private: template<class U> static constexpr std::true_type exists( decltype( std::declval<U>() == std::declval<U>() )* = nullptr ) noexcept; template<class U> static constexpr std::false_type exists( ... ) noexcept; public: static constexpr bool value = decltype( exists<T>( nullptr ) )::value; };
./events/handlers/ab abstracteventhandler.hpp #pragma once #include "eventhandlerptr.h" namespace events { namespace handlers { template<class ...TParams> class AbstractEventHandler { using MyType = AbstractEventHandler<TParams...>; public: virtual ~AbstractEventHandler() {} virtual void call( TParams... params ) = 0; bool operator==( const MyType& other ) const noexcept { return isEquals( other ); } bool operator!=( const MyType& other ) const noexcept { return !( *this == other ); } protected: AbstractEventHandler() {} virtual bool isEquals( const MyType& other ) const noexcept = 0; }; } // handlers } // events
./events/handlers/eventhandlerptr.h #pragma once #include <memory> namespace events { namespace handlers { template<class ...TParams> class AbstractEventHandler; template<class ...Types> using TEventHandlerPtr = std::shared_ptr<AbstractEventHandler<Types...>>; } // handlers } // events
./events/handlers/functoreventhandler.hpp #pragma once #include <memory> #include <assert.h> #include "abstracteventhandler.hpp" #include "../helpers/is_equatable.hpp" namespace events { namespace handlers { namespace { template<class TFunctor, class ...TParams> struct IsFunctorParamsCompatible { private: template<class TCheckedFunctor, class ...TCheckedParams> static constexpr std::true_type exists( decltype( std::declval<TCheckedFunctor>()( std::declval<TCheckedParams>()... ) )* = nullptr ) noexcept; template<class TCheckedFunctor, class ...TCheckedParams> static constexpr std::false_type exists( ... ) noexcept; public: static constexpr bool value = decltype( exists<TFunctor, TParams...>( nullptr ) )::value; }; } // template<class TFunctor> class FunctorHolder; template<class TFunctor, class ...TParams> class FunctorEventHandler : public AbstractEventHandler<TParams...> { using MyType = FunctorEventHandler<TFunctor, TParams...>; using TFunctorHolderPtr = std::shared_ptr<FunctorHolder<TFunctor>>; public: FunctorEventHandler( TFunctorHolderPtr functorHolder ) : AbstractEventHandler<TParams...>(), m_functorHolder( functorHolder ) { assert( m_functorHolder != nullptr ); } virtual void call( TParams... params ) override { static_assert( IsFunctorParamsCompatible<TFunctor, TParams...>::value, "Event and functor arguments are not compatible" ); m_functorHolder->m_functor( params... ); } protected: virtual bool isEquals( const AbstractEventHandler<TParams...>& other ) const noexcept override { const MyType* _other = dynamic_cast<const MyType*>( &other ); return ( _other != nullptr && *m_functorHolder == *_other->m_functorHolder ); } private: TFunctorHolderPtr m_functorHolder; }; namespace { template<class TEqu, class TEnabled = void> struct EqualityChecker; template<class TEquatable> struct EqualityChecker<TEquatable, typename std::enable_if<is_equatable<TEquatable>::value>::type> { static constexpr bool isEquals( const TEquatable& operand1, const TEquatable& operand2 ) noexcept { return ( operand1 == operand2 ); } }; template<class TNonEquatable> struct EqualityChecker<TNonEquatable, typename std::enable_if<!is_equatable<TNonEquatable>::value>::type> { static constexpr bool isEquals( const TNonEquatable& operand1, const TNonEquatable& operand2 ) noexcept { return ( &operand1 == &operand2 ); } }; } // template<class TFunctor> class FunctorHolder { using MyType = FunctorHolder<TFunctor>; public: template<class ...TCallParams> operator TEventHandlerPtr<TCallParams...>() { return TEventHandlerPtr<TCallParams...>( new FunctorEventHandler<TFunctor, TCallParams...>( m_me.lock() ) ); } bool operator==( const MyType& other ) const noexcept { return EqualityChecker<TFunctor>::isEquals( m_functor, other.m_functor ); } bool operator!=( const MyType& other ) const noexcept { return !( *this == other ); } template<class TFunctor> static std::shared_ptr<MyType> create( TFunctor&& functor ) { std::shared_ptr<MyType> result( new MyType( functor ) ); result->m_me = result; return result; } private: FunctorHolder( TFunctor& functor ) : m_functor( functor ), m_me() { } TFunctor& m_functor; std::weak_ptr<MyType> m_me; template<class TFunctor, class ...TParams> friend class FunctorEventHandler; }; template<class TFunctor> std::shared_ptr<FunctorHolder<TFunctor>> createFunctorEventHandler( TFunctor&& functor ) { return FunctorHolder<TFunctor>::create( functor ); } } // handlers } // events #define FUNCTOR_HANDLER( Functor ) ::events::handlers::createFunctorEventHandler( Functor ) #define LAMBDA_HANDLER( Lambda ) FUNCTOR_HANDLER( Lambda ) #define STD_FUNCTION_HANDLER( StdFunction ) FUNCTOR_HANDLER( StdFunction ) #define FUNCTION_HANDLER( Function ) FUNCTOR_HANDLER( &Function )
./events/handlers/methodeventhandler.hpp #pragma once #include <memory> #include <assert.h> #include "abstracteventhandler.hpp" namespace events { namespace handlers { namespace { template<class TMethodHolder, class ...TParams> struct IsMethodParamsCompatible { private: template<class TCheckedMethodHolder, class ...TCheckedParams> static constexpr std::true_type exists( decltype( ( std::declval<TCheckedMethodHolder>().m_object.*std::declval<TCheckedMethodHolder>().m_method )( std::declval<TCheckedParams>()... ) )* = nullptr ) noexcept; template<class TCheckedMethodHolder, class ...TCheckedParams> static constexpr std::false_type exists( ... ) noexcept; public: static constexpr bool value = decltype( exists<TMethodHolder, TParams...>( nullptr ) )::value; }; } // template<class TMethodHolder, class ...TParams> class MethodEventHandler : public AbstractEventHandler<TParams...> { using MyType = MethodEventHandler<TMethodHolder, TParams...>; using TMethodHolderPtr = std::shared_ptr<TMethodHolder>; public: MethodEventHandler( TMethodHolderPtr methodHolder ) : AbstractEventHandler<TParams...>(), m_methodHolder( methodHolder ) { assert( m_methodHolder != nullptr ); } virtual void call( TParams... params ) override { static_assert( IsMethodParamsCompatible<TMethodHolder, TParams...>::value, "Event and method arguments are not compatible" ); ( m_methodHolder->m_object.*m_methodHolder->m_method )( params... ); } protected: virtual bool isEquals( const AbstractEventHandler<TParams...>& other ) const noexcept override { const MyType* _other = dynamic_cast<const MyType*>( &other ); return ( _other != nullptr && *m_methodHolder == *_other->m_methodHolder ); } private: TMethodHolderPtr m_methodHolder; }; template<class TObject, class TResult, class ...TParams> class MethodHolder { using MyType = MethodHolder<TObject, TResult, TParams...>; using TMethod = TResult( TObject::* )( TParams... ); public: template<class ...TCallParams> operator TEventHandlerPtr<TCallParams...>() { return TEventHandlerPtr<TCallParams...>( new MethodEventHandler<MyType, TCallParams...>( m_me.lock() ) ); } bool operator==( const MyType& other ) const noexcept { return ( &m_object == &other.m_object && m_method == other.m_method ); } bool operator!=( const MyType& other ) const noexcept { return !( *this == other ); } template<class TObject, class ...TParams> static std::shared_ptr<MyType> create( TObject& object, TMethod method ) { std::shared_ptr<MyType> result( new MyType( object, method ) ); result->m_me = result; return result; } private: MethodHolder( TObject& object, TMethod method ) : m_object( object ), m_method( method ) { assert( m_method != nullptr ); } TObject& m_object; TMethod m_method; std::weak_ptr<MyType> m_me; template<class TMethodHolder, class ...TParams> friend class MethodEventHandler; template<class TMethodHolder, class ...TParams> friend struct IsMethodParamsCompatible; }; template<class TObject, class TResult, class ...TParams> std::shared_ptr<MethodHolder<TObject, TResult, TParams...>> createMethodEventHandler( TObject& object, TResult( TObject::*method )( TParams... ) ) { return MethodHolder<TObject, TResult, TParams...>::create( object, method ); } } // handlers } // events #define METHOD_HANDLER( Object, Method ) ::events::handlers::createMethodEventHandler( Object, &Method ) #define MY_METHOD_HANDLER( Method ) METHOD_HANDLER( *this, Method )
./events/handlers/handlercast.hpp #pragma once #include <memory> #include "eventhandlerptr.h" namespace events { namespace handlers { template<class TSome> struct HandlerCast { template<class ...Types> static constexpr TEventHandlerPtr<Types...> cast( TSome& some ) { return static_cast<TEventHandlerPtr<Types...>>( some ); } }; template<class TPtr> struct HandlerCast<std::shared_ptr<TPtr>> { template<class ...Types> static constexpr TEventHandlerPtr<Types...> cast( std::shared_ptr<TPtr> some ) { return HandlerCast<TPtr>::cast<Types...>( *some ); } }; } // handlers } // events
./events/event.hpp #pragma once #include <type_traits> #include <list> #include <memory> #include <shared_mutex> #include <algorithm> #include <assert.h> #include "handlers/abstracteventhandler.hpp" #include "handlers/eventhandlerptr.h" #include "handlers/handlercast.hpp" #include "joins/eventjoinwrapper.hpp" namespace events { namespace joins { template<class ...TParams> class HandlerEventJoin; } template<class ...TParams> class IEvent { public: template<class TSome> EventJoin operator+=( TSome&& some ) { EventJoin result( *this, std::forward<TSome>( some ) ); result.join(); return result; } template<class TSome> bool operator-=( TSome&& some ) { return removeHandler( handlers::HandlerCast<TSome>::cast<TParams...>( some ) ); } protected: using TMyEventHandlerPtr = handlers::TEventHandlerPtr<TParams...>; IEvent() {} virtual bool isHandlerAdded( const TMyEventHandlerPtr& eventHandler ) const = 0; virtual bool addHandler( TMyEventHandlerPtr eventHandler ) = 0; virtual bool removeHandler( TMyEventHandlerPtr eventHandler ) = 0; friend class joins::HandlerEventJoin<TParams...>; }; template<class ...TParams> class TEvent : public IEvent<TParams...> { using TEventHandlerIt = typename std::list<TMyEventHandlerPtr>::const_iterator; public: TEvent() : m_handlers(), m_currentIt(), m_isCurrentItRemoved( false ), m_handlerListMutex() { } void operator()( TParams... params ) { m_handlerListMutex.lock_shared(); m_isCurrentItRemoved = false; m_currentIt = m_handlers.begin(); while( m_currentIt != m_handlers.end() ) { m_handlerListMutex.unlock_shared(); ( *m_currentIt )->call( params... ); m_handlerListMutex.lock_shared(); if( m_isCurrentItRemoved ) { m_isCurrentItRemoved = false; TEventHandlerIt removedIt = m_currentIt; ++m_currentIt; deleteHandler( removedIt ); } else { ++m_currentIt; } } m_handlerListMutex.unlock_shared(); } protected: virtual bool isHandlerAdded( const TMyEventHandlerPtr& eventHandler ) const override { std::shared_lock<std::shared_mutex> _handlerListMutexLock( m_handlerListMutex ); return ( findEventHandler( eventHandler ) != m_handlers.end() ); } virtual bool addHandler( TMyEventHandlerPtr eventHandler ) override { std::unique_lock<std::shared_mutex> _handlerListMutexLock( m_handlerListMutex ); if( findEventHandler( eventHandler ) == m_handlers.end() ) { m_handlers.push_back( std::move( eventHandler ) ); return true; } return false; } virtual bool removeHandler( TMyEventHandlerPtr eventHandler ) override { std::unique_lock<std::shared_mutex> _handlerListMutexLock( m_handlerListMutex ); auto it = findEventHandler( eventHandler ); if( it != m_handlers.end() ) { if( it == m_currentIt ) m_isCurrentItRemoved = true; else deleteHandler( it ); return true; } return false; } private: // 'm_handlerListMutex' inline TEventHandlerIt findEventHandler( const TMyEventHandlerPtr& eventHandler ) const noexcept { return std::find_if( m_handlers.cbegin(), m_handlers.cend(), [ &eventHandler ]( const TMyEventHandlerPtr& oneHandler ) { return ( *oneHandler == *eventHandler ); } ); } // 'm_handlerListMutex' inline void deleteHandler( TEventHandlerIt it ) { m_handlers.erase( it ); } std::list<TMyEventHandlerPtr> m_handlers; // 'm_handlerListMutex' mutable TEventHandlerIt m_currentIt; mutable bool m_isCurrentItRemoved; mutable std::shared_mutex m_handlerListMutex; }; } // events
./events/joins/abstracteventjoin.h #pragma once namespace events { namespace joins { class AbstractEventJoin { public: virtual ~AbstractEventJoin(); virtual bool isJoined() const = 0; virtual bool join() = 0; virtual bool unjoin() = 0; protected: AbstractEventJoin(); }; }
./events/joins/abstracteventjoin.cpp #include "abstracteventjoin.h" namespace events { namespace joins { AbstractEventJoin::AbstractEventJoin() { } AbstractEventJoin::~AbstractEventJoin() { } }
./events/joins/handlereventjoin.h #pragma once #include "abstracteventjoin.h" #include "../handlers/eventhandlerptr.h" namespace events { template<class ...TParams> class IEvent; namespace joins { template<class ...TParams> class HandlerEventJoin : public AbstractEventJoin { public: HandlerEventJoin( IEvent<TParams...>& _event, ::events::handlers::TEventHandlerPtr<TParams...> handler ) : AbstractEventJoin(), m_event( _event ), m_handler( handler ) { } virtual inline bool isJoined() const override; virtual inline bool join() override; virtual inline bool unjoin() override; private: IEvent<TParams...>& m_event; ::events::handlers::TEventHandlerPtr<TParams...> m_handler; }; } // joins } // events
./events/joins/handlereventjoin.hpp #pragma once #include "handlereventjoin.h" #include "../event.hpp" namespace events { namespace joins { template<class ...TParams> bool HandlerEventJoin<TParams...>::isJoined() const { return m_event.isHandlerAdded( m_handler ); } template<class ...TParams> bool HandlerEventJoin<TParams...>::join() { return m_event.addHandler( m_handler ); } template<class ...TParams> bool HandlerEventJoin<TParams...>::unjoin() { return m_event.removeHandler( m_handler ); } } // joins } // events
./events/joins/eventjoinwrapper.h #pragma once #include <memory> #include "../handlers/eventhandlerptr.h" namespace events { template<class ...TParams> class IEvent; namespace joins { class AbstractEventJoin; class EventJoinWrapper { public: template<class TSome, class ...TParams> inline EventJoinWrapper( IEvent<TParams...>& _event, TSome&& handler ); constexpr EventJoinWrapper() noexcept; EventJoinWrapper( EventJoinWrapper&& other ) noexcept; EventJoinWrapper( EventJoinWrapper& other ) noexcept; EventJoinWrapper& operator=( EventJoinWrapper&& other ) noexcept; EventJoinWrapper& operator=( const EventJoinWrapper& other ) noexcept; operator bool() const; bool isAssigned() const; bool isJoined() const; bool join(); bool unjoin(); private: std::shared_ptr<AbstractEventJoin> m_eventJoin; }; } // joins using EventJoin = joins::EventJoinWrapper; } // events
./events/joins/eventjoinwrapper.hpp #pragma once #include "eventjoinwrapper.h" #include "handlereventjoin.h" #include "../handlers/handlercast.hpp" namespace events { namespace joins { template<class TSome, class ...TParams> EventJoinWrapper::EventJoinWrapper( IEvent<TParams...>& _event, TSome&& handler ) : m_eventJoin( std::make_shared<HandlerEventJoin<TParams...>>( _event, ::events::handlers::HandlerCast<TSome>::cast<TParams...>( handler ) ) ) { } } // joins } // events
./events/joins/eventjoinwrapper.cpp #include "eventjoinwrapper.h" #include <type_traits> #include "abstracteventjoin.h" namespace events { namespace joins { constexpr EventJoinWrapper::EventJoinWrapper() noexcept : m_eventJoin( nullptr ) { } EventJoinWrapper::EventJoinWrapper( EventJoinWrapper&& other ) noexcept : m_eventJoin( std::move( other.m_eventJoin ) ) { } EventJoinWrapper::EventJoinWrapper( EventJoinWrapper& other ) noexcept : m_eventJoin( other.m_eventJoin ) { } EventJoinWrapper& EventJoinWrapper::operator=( EventJoinWrapper&& other ) noexcept { m_eventJoin = std::move( other.m_eventJoin ); return *this; } EventJoinWrapper& EventJoinWrapper::operator=( const EventJoinWrapper& other ) noexcept { m_eventJoin = other.m_eventJoin; return *this; } EventJoinWrapper::operator bool() const { return isJoined(); } bool EventJoinWrapper::isAssigned() const { return ( m_eventJoin != nullptr ); } bool EventJoinWrapper::isJoined() const { return ( m_eventJoin != nullptr && m_eventJoin->isJoined() ); } bool EventJoinWrapper::join() { return ( m_eventJoin != nullptr ? m_eventJoin->join() : false ); } bool EventJoinWrapper::unjoin() { return ( m_eventJoin != nullptr ? m_eventJoin->unjoin() : false ); } } // joins } // events
UPD1. هنا وفي وقت سابق من المقالة ، يتم إعطاء التعليمات البرمجية المكتوبة تحت VC ++ 14 . للتوافق مع المترجمين الآخرين ، من الأفضل أخذ الكود من الرابط. شكر خاص إلى الغشاش لتوفير التوافق مع دول مجلس التعاون الخليجي .UPD2. شكرا lexxmark ، الذي رأى حفرة في موضوع من حيث عدة استدعاءات الأحداث في وقت واحد.تحسينات طفيفة namespace { template<class ...TParams> struct TypeHelper { using TEventHandlerPtr = handlers::TEventHandlerPtr<TParams...>; using TEventHandlerIt = typename std::list<TEventHandlerPtr>::const_iterator; }; }
(, , )
HandlerRunner , . , :
currentIt ( )
wasRemoving (, ).
HandlerRunner' operator() ; (, ) ,
EventCore . T.O. , , , , , .
UPD3. بفضل isnullxbh ، تم العثور على خطأ آخر. وهو مرتبط بتخزين غير لائق والوصول اللاحق إلى الكائنات التي تم تمريرها بواسطة rvalue (بشكل رئيسي تعبيرات لامدا).تصحيح,
lvalue ,
lvalue -, ,
rvalue , (, ). :
template<class TSome> struct ObjectSaver; template<class LValue> struct ObjectSaver<LValue&> { using TObject = LValue&; }; template<class RValue> struct ObjectSaver<RValue&&> { using TObject = RValue; };
Holder (
lvalue rvalue ), , «» .
type erasing (
). ,
Holder' .
template<class TBase> struct AbstractInnerHolder { virtual ~AbstractInnerHolder() {} virtual inline TBase& get() = 0; inline const TBase& get() const { return const_cast<AbstractInnerHolder<TBase>&>( *this ).get(); } }; template<class TBase, class TInner> struct TInnerHolder : public AbstractInnerHolder<TBase> { using TInnerObject = typename ObjectSaver<TInner>::TObject; TInnerHolder( TInner _inner ) : AbstractInnerHolder<TBase>(), inner( std::forward<TInner>( _inner ) ) { } virtual inline TBase& get() override { return static_cast<TBase&>( inner ); } TInnerObject inner; }; template<class TAssignBase, class TArgInner> AbstractInnerHolder<TAssignBase>& createInnerHolder( TArgInner&& inner ) { using TAssignInner = decltype( inner ); return *new TInnerHolder<TAssignBase, TAssignInner>( std::forward<TArgInner>( inner ) ); }
Holder' .
MethodHolder' .
template<class TObject, class TResult, class ...TParams> class MethodHolder { . . . using MyType = MethodHolder<TObject, TResult, TParams...>; using TMethod = TResult( TObject::* )( TParams... ); public: ~MethodHolder() { delete &m_innerHolder; } bool operator==( const MyType& other ) const { return ( &m_innerHolder.get() == &other.m_innerHolder.get() && m_method == other.m_method ); } template<class TArgObject> static std::shared_ptr<MyType> create( TArgObject&& object, TMethod method ) { std::shared_ptr<MyType> result( new MyType( std::forward<TArgObject>( object ), method ) ); result->m_me = result; return result; } private: template<class TArgObject> MethodHolder( TArgObject&& object, TMethod method ) : m_innerHolder( createInnerHolder<TObject>( std::forward<TArgObject>( object ) ) ), m_method( method ) { assert( m_method != nullptr ); } AbstractInnerHolder<TObject>& m_innerHolder; TMethod m_method; std::weak_ptr<MyType> m_me; . . . };
namespace { template<class TMethodHolder, class ...TParams> struct IsMethodParamsCompatible { private: template<class TCheckedMethodHolder, class ...TCheckedParams> static constexpr std::true_type exists( decltype( ( std::declval<TCheckedMethodHolder>().m_innerHolder.get().*std::declval<TCheckedMethodHolder>().m_method )( std::declval<TCheckedParams>()... ) )* = nullptr ); . . . }; }
template<class TObject, class TResult, class ...TParams> std::shared_ptr<MethodHolder<typename std::decay<TObject>::type, TResult, TParams...>> createMethodEventHandler( TObject&& object, TResult( std::decay<TObject>::type::*method )( TParams... ) ) { return MethodHolder<std::decay<TObject>::type, TResult, TParams...>::create( std::forward<TObject>( object ), method ); }
تم.
FunctorHolder . . - .
مقارنة PS مع آلية إشارة / فتحة Qt
أعتقد أنني لن أكون مخطئا إذا قلت أن Qt هو إطار شائع جدًا للتطوير في C ++ . من بين أمور أخرى ، لديها أيضًا آلية معالجة الأحداث الخاصة بها ، حيث توجد إشارات كمناظرات للأحداث وفتحات كمناظرة للمعالجات. يتم تنفيذه باستخدام برنامج Meta-Object Compiler ، وهو جزء من نظام Meta-Object System الأكثر عمومية ، والذي يتم تنفيذه بدوره باستخدام الوظيفة الإضافية المستخدمة في Qt عبر C ++ .ميزات كلا التطبيقين:
- القدرة على ربط الإشارات (الأحداث) بالأساليب (وظائف الأعضاء) ، والوظائف المساعدة والوظائف ؛
- () (), ( lvalue , rvalue );
- ( );
- () ( ).
Qt :
- ;
Qt ; «», , « » ; , ; , ;
- ;
Qt ( ) ( ); , Qt::UniqueConnection ; , , , Qt ;
- , ;
Qt::QueuedConnection Qt::BlockingQueuedConnection ; () ; (); , , ; أي , ; , .
Qt :
- QObject ;
, , QObject , , , ( : Virtual inheritance with QObject is not supported. ); , , ;
- template';
, public - QObject ; moc' ; ,
, #include <QObject> class AbstractProperty : public QObject { Q_OBJECT protected: AbstractProperty(); signals: void valueChanged(); }; template<class TSource> class TProperty : public AbstractProperty { public: TProperty( const TSource& value = TSource() ) : AbstractProperty(), m_value( value ) { } const TSource& value() const { return m_value; } void setValue( const TSource& newValue ) { if( newValue != m_value ) { m_value = newValue; emit valueChanged(); } } private: TSource m_value; };
,
valueChanged ( , ) , .
, , ;
.cpp-;
;
- QMetaObject::Connection ;
, Qt ( ) , ; () , , ; , ; Qt ;
- استخدام التعليمات البرمجية التي تم إنشاؤها بشكل إضافي بواسطة moc ؛
هذا بالفعل موضوعي تمامًا ، ولكن القرار هو ، حيث يوجد لكل فئة تستخدم إشارات وفتحات (لا تكون الفتحات دائمًا) هناك العديد من الملفات (حسب الملف لكل تكوين) التي تسبب بعض الإزعاج ؛ ولكن بصراحة ، هذا هو العيب الأصغر.
من المهم أن نلاحظ أن هذه المقارنة مع كيو تي هي مع ذلك ذاتية للغاية ولا تهدف إلى تمجيد أو إدانة هذا الإطار. يجب أن نتذكر أنه بالإضافة إلى آلية الإشارة / الفتحة ، توفر Qt وظائف رائعة ، سواء باستخدام هذه الآلية ولا تعتمد عليها. على أي حال ، الأمر متروك لك دائمًا لتحديد ما يجب استخدامه وما لا تستخدمه.