C ++ 11 dan penanganan acara

Saya pikir bahwa pemrosesan peristiwa sebagai cara interaksi antara objek di OOP diketahui hampir semua orang yang pernah menyentuh OOP setidaknya sekali. Setidaknya, pendekatan ini sangat nyaman dalam cakupan tugas yang sangat luas, menurut saya. Dalam banyak bahasa pemrograman, mesin penanganan acara adalah bawaan; Namun, dalam C ++ tidak ada mekanisme seperti itu. Mari kita lihat apa yang dapat Anda lakukan.

Pengantar singkat


Suatu peristiwa adalah sesuatu yang dapat terjadi pada beberapa objek dalam kondisi tertentu (misalnya, dengan tombol ketika Anda mengkliknya dengan mouse). Entitas lain mungkin perlu mengetahui hal ini; lalu mereka berlangganan acara tersebut . Dalam hal ini, ketika suatu peristiwa terjadi, pawang dari objek pihak ketiga yang berlangganan acara tersebut disebut; Dengan demikian, ia memiliki kesempatan untuk mengeksekusi beberapa kode, mis. menanggapi suatu acara. Demikian pula, suatu objek dapat berhenti berlangganan dari suatu peristiwa jika tidak lagi ingin menanggapinya. Akibatnya, kami memiliki banyak objek yang dapat dihubungkan satu sama lain menggunakan peristiwa salah satunya dan reaksi terhadap peristiwa lain ini.

Sesuatu seperti itu, meskipun semua orang tahu itu.

Implementasi yang paling sederhana


Tampaknya mudah untuk menerapkan perilaku semacam itu. Dan mungkin terlihat seperti ini:

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 ) 

Penerapan kasus ini harus dalam bentuk:

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

Tentu saja, metode penangan (-fungsi-anggota kelas) tidak akan menjadi satu-satunya jenis penangan, tetapi lebih pada nanti.

Segalanya tampak nyaman, kompak dan hebat. Namun meski ada sejumlah kekurangan.

Perbandingan Handler


Untuk menerapkan berhenti berlangganan dari suatu peristiwa, perlu untuk menambahkan kemungkinan perbandingan ke penangan (dengan == dan ! == ). Penangan yang memanggil metode yang sama (fungsi anggota kelas) dari objek yang sama (mis., Instance yang sama dari kelas yang sama) akan dianggap sama.

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

Kemudian kita akan dapat menghapus penangan dari berlangganan acara. Dalam hal ini, perlu untuk melarang menambahkan penangan yang sama (sama).

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

Di sini, fungsi tambah / hapus dari handler mengembalikan true jika berhasil dan salah jika tindakan yang sesuai (tambah atau hapus) tidak dilakukan.

Ya, use case dengan perbandingan melibatkan pembuatan penangan sementara yang tidak ditambahkan di mana pun yang tidak dihapus di mana pun. Tetapi lebih lanjut tentang itu nanti.

Bisakah ini digunakan? Belum sepenuhnya diimplementasikan.

Menghapus pawang di dalam pawang


Jadi, kami segera menemukan crash selama eksekusi kode, di mana pawang berhenti berlangganan dari acara tersebut (saya pikir itu bukan kasus penggunaan paling langka ketika pawang memotong sendiri dalam kondisi apa pun):

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

Masalah muncul karena alasan yang sangat sederhana:

  • acara dipicu dan mulai untuk mengulangi (menggunakan iterator) penangan, memanggil mereka;
  • handler berikutnya di dalam dirinya menyebabkan dirinya dihapus;
  • acara menghapus handler yang diberikan, membuat iterator yang sesuai tidak valid;
  • setelah penyelesaian penangan ini, acara kembali untuk menghitung yang lain, namun, iterator saat ini (terkait dengan penangan jarak jauh) sudah tidak valid;
  • acara ini mencoba mengakses iterator yang tidak valid, menyebabkan jatuh.

Oleh karena itu, perlu untuk memeriksa kasus ketika daftar penangan dapat diubah, yang akan menyebabkan pembatalan iterator; dan kemudian menerapkan perlindungan baca untuk iterator tersebut.

Keuntungan std :: list 'dalam aplikasi ini adalah kenyataan bahwa ketika menghapusnya hanya membuat satu iterator menjadi tidak valid - pada elemen yang dihapus (memengaruhi, misalnya, yang berikut); dan menambahkan elemen tidak menyebabkan pembatalan iterator sama sekali. Jadi, kita perlu mengontrol satu-satunya kasus: menghapus elemen yang iteratornya saat ini dalam penghitungan elemen saat ini. Dalam hal ini, Anda dapat, misalnya, tidak menghapus elemen, tetapi cukup tandai bahwa elemen saat ini akan dihapus, dan biarkan dilakukan di dalam penghitungan elemen.

Seseorang dapat segera menjalankan implementasi ini, tetapi saya mengusulkan untuk menyelesaikan masalah ini bersama dengan yang berikut.

Keamanan benang


Secara potensial, panggilan ke tiga fungsi yang mungkin - menambah, menghapus, dan mengurutkan (ketika suatu kejadian dipicu) penangan - dimungkinkan dari utas yang berbeda secara acak. Ini menciptakan seluruh bidang kemungkinan untuk "persimpangan" mereka dalam waktu, "tumpang tindih" dari eksekusi mereka satu sama lain dan sebagai hasilnya program jatuh. Mari kita coba hindari ini; Mutex adalah segalanya bagi kita .

 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: //      'm_handlerListMutex' inline TEventHandlerIt findEventHandler( TEventHandler& eventHandler ) const { return std::find_if( m_handlers.cbegin(), m_handlers.cend(), [ &eventHandler ]( const TEventHandler* oneHandler ) { return ( *oneHandler == eventHandler ); } ); } //      'm_handlerListMutex' inline void deleteHandler( TEventHandlerIt it ) { TEventHandler* removedEventHandler = *it; m_handlers.erase( it ); delete removedEventHandler; } std::list<TEventHandler*> m_handlers; //    'm_handlerListMutex' mutable TEventHandlerIt m_currentIt; mutable bool m_isCurrentItRemoved; mutable std::shared_mutex m_handlerListMutex; }; 

Jangan lupa untuk membiarkan jendela terbuka hampir setiap kali Anda memanggil setiap penangan. Ini diperlukan agar di dalam handler dimungkinkan untuk mengakses acara dan mengubahnya (misalnya, menambah / menghapus handler) tanpa menyebabkan kebuntuan . Anda tidak boleh takut dengan validitas data, karena, seperti yang kami ketahui, satu-satunya hal yang mengarah ke ini adalah penghapusan elemen saat ini, dan situasi ini telah diproses.
UPD1. Terima kasih Cheater , menyarankan agar std :: shared_mutex hanya muncul di C ++ 17 (dan std :: shared_lock hanya di C ++ 14 ). Mereka yang sangat kritis mungkin harus melakukan dengan std :: mutex .
UPD2. Selanjutnya, tentang keamanan utas (tanpa menjaga urutan narasi).

Masalah Visibilitas Acara


Saat menggunakan acara sebagai anggota kelas, tampaknya logis untuk membuatnya publik , sehingga objek pihak ketiga dapat menambah / menghapus penangannya. Namun, ini akan menghasilkan operator () , mis. panggilan acara juga akan dapat diakses dari luar, yang dalam beberapa kasus mungkin tidak dapat diterima. Kami akan memecahkan masalah ini dengan mengisolasi dari kelas acara ( TEvent <...> ) antarmuka abstrak yang hanya ditujukan untuk menangani penangan.

 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 { // ,     'TEvent::operator+=' } virtual bool removeHandler( TEventHandler& eventHandler ) override { // ,     'TEvent::operator-=' } . . . }; 

Sekarang kita dapat membagi ke dalam cakupan yang berbeda bagian dari acara yang bertanggung jawab untuk bekerja dengan penangan, dan bagian yang bertanggung jawab untuk menyebutnya.

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

Dengan demikian, sekarang objek pihak ketiga dapat menambah / menghapus penangan mereka melalui TestWindow :: onButtonClick , namun mereka tidak akan dapat memicu acara ini sendiri. Panggilan sekarang dapat dilakukan hanya di dalam kelas TestWindow (dan turunannya, jika ruang lingkup acara, sebagai contoh, dilindungi ).

Kode trivial perlahan mulai berubah menjadi sesuatu yang mengerikan, tetapi ini bukan akhir.

Cocokkan parameter acara dan penangannya


Dalam implementasi saat ini, acara dan salah satu penangannya harus memiliki daftar parameter yang benar-benar sesuai. Hal ini menyebabkan sejumlah kerugian.

Yang pertama. Misalkan kita memiliki templat kelas di mana ada acara dengan parameter templat.

 template<class TSource> class MyClass { . . . public: TEvent<const TSource&> onValueChanged; . . . }; 

Karena fakta bahwa jenis yang akan digunakan di sini tidak diketahui sebelumnya, masuk akal untuk mengirimkannya melalui tautan konstan, dan bukan berdasarkan nilai. Namun, sekarang untuk implementasi apa pun, bahkan dengan tipe fundamental, penangan yang sesuai harus ada.

 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 ); // OK myBoolClass.onValueChanged += METHOD_HANDLER( myHandlerClass, MyHandlerClass::handleValueChanged2 ); // compile error 

Saya ingin dapat menghubungkan penangan formulir MyHandlerClass :: handleValueChanged2 ke acara serupa, tetapi sejauh ini tidak ada kemungkinan seperti itu.

Yang kedua. Mari kita coba mengimplementasikan functor handler yang mirip dengan metode handler yang ada (-fungsi-anggota kelas).

 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 ) 

Sekarang cobalah untuk mengacaukannya ke beberapa acara.

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

Hasilnya akan menjadi kesalahan kompilasi. Untuk fungsi createFunctorEventHandler, kompiler tidak dapat menyimpulkan jenis TParams ... dari satu-satunya argumen ke fungsi ini - functor itu sendiri. Functor benar-benar tidak mengandung informasi tentang jenis penangan yang dibuat berdasarkan itu. Satu-satunya hal yang dapat dilakukan dalam situasi ini adalah menulis sesuatu seperti:

 testWindow.onButtonClick += createFunctorEventHandler<ClickEventHandler, const std::string&, unsigned int>( clickEventHandler ); 

Tetapi Anda tidak ingin melakukan ini sama sekali.

Menghubungkan suatu acara ke berbagai jenis penangan


Jadi, ada Wishlist, terserah implementasi. Kami akan mempertimbangkan situasi dengan menggunakan contoh dari handler functor; metode handler (-fungsi-anggota kelas) akan diperoleh dengan cara yang sama.

Karena atas dasar functor saja tidak mungkin untuk mengatakan apa daftar parameter dari handler yang sesuai, maka kita tidak akan melakukan ini. Pertanyaan ini menjadi relevan bukan pada saat pawang dibuat, tetapi pada saat mencoba melampirkannya pada peristiwa tertentu. Dan ya, ini adalah dua poin yang berbeda. Gagasan ini dapat diimplementasikan sebagai berikut:

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

Singkatnya, pemisahan saat-saat penciptaan pawang dan keterikatannya dengan peristiwa di sini lebih terasa daripada sebelumnya. Ini menghindari masalah yang dijelaskan dalam paragraf sebelumnya. Jenis pengujian kompatibilitas akan terjadi ketika mencoba untuk melampirkan FunctorHolder spesifik ke FunctorEventHandler tertentu, atau lebih tepatnya, membuat turunan dari FunctorEventHandler <...> Kelas dengan tipe functor yang sangat spesifik; dan di kelas ini akan ada baris kode m_functorHolder.m_functor (params ...); , yang tidak bisa dikompilasi untuk sekumpulan tipe yang tidak kompatibel dengan functor (atau jika itu bukan functor sama sekali, mis. objek yang tidak memiliki operator () ).

Saya ulangi bahwa masalah menghapus objek sementara akan dibahas di bawah ini. Selain itu, perlu dicatat bahwa sekelompok makro untuk setiap kasus dibuat, pertama, untuk menunjukkan kemampuan jenis penangan ini, dan kedua, dalam hal kemungkinan modifikasi dari salah satu dari mereka dengan file.

Periksa hasilnya.

 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 ); // ok testWindow.onButtonClick += FUNCTOR_HANDLER( functor2 ); // ok testWindow.onButtonClick += FUNCTOR_HANDLER( functor3 ); // ok testWindow.onButtonClick += FUNCTOR_HANDLER( functor4 ); // ok testWindow.onButtonClick += FUNCTOR_HANDLER( functor5 ); // compile error testWindow.onButtonClick += FUNCTOR_HANDLER( functor6 ); // ok testWindow.onButtonClick += FUNCTOR_HANDLER( functor7 ); // compile error . . . } 

Kesalahan kompilasi terjadi ketika mencoba mengkonversi salah satu parameter dari nilai konstanta ke nilai semula . Mengubah dari nilai menjadi tidak penting Nilai tidak menyebabkan kesalahan, meskipun perlu dicatat bahwa itu menciptakan potensi ancaman self-shot di kaki: pawang akan dapat mengubah variabel yang disalin ke dalam tumpukan, yang dengan senang hati akan dihapus ketika pawang ini keluar.

Secara umum, pesan kesalahan akan terlihat seperti ini:

 Error C2664 'void Functor5::operator ()(std::string &,unsigned int &)': cannot convert argument 1 from 'const std::string' to 'std::string &' 

Untuk kejelasan yang lebih besar, saat menggunakan acara dan penangan dalam kode pihak ketiga, Anda dapat menambahkan pesan kesalahan Anda sendiri. Ini akan membutuhkan penulisan struktur pendukung kecil (saya akui, saya melihat pendekatan serupa di suatu tempat):

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

Pekerjaan ini didasarkan pada mekanisme SFINAE . Singkatnya, suatu usaha dilakukan untuk mengkompilasi fungsi pertama ada , namun, jika ini tidak berfungsi karena ketidakcocokan argumen (atau tidak adanya operator () dari apa yang disahkan sebagai functor), kompiler tidak melakukan kesalahan, tetapi hanya mencoba mengkompilasi fungsi kedua; kami melakukan segalanya sehingga kompilasi selalu berhasil, dan kemudian, berdasarkan fakta fungsi yang dikompilasi, kami menyimpulkan (dengan menulis hasilnya ke nilai ) tentang kompatibilitas argumen untuk jenis yang diberikan.

Sekarang pesan kesalahan akan terlihat seperti ini:

 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 &' 

Selain pesan kesalahan tambahan yang lebih informatif, pendekatan ini memecahkan masalah konversi argumen dari nilai ke nilai tidak masuk akal : sekarang ini menyebabkan kesalahan ketidakcocokan argumen, mis. Mencoba untuk menambahkan pengendali functor6 dari contoh di atas menghasilkan kesalahan waktu kompilasi.
UPD Penyempitan (tanpa mempertahankan urutan narasi).

Perbandingan Functor


Karena perubahan dalam kelas handler, implementasi contoh pembandingan dari kelas ini akan sedikit berubah. Sekali lagi, saya hanya akan menyediakan implementasi dari functor handler, karena metode-handler (-fungsi-anggota kelas) akan terlihat serupa.

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

Pada ini, kesamaan dalam implementasi akhir perbandingan dan bagian dimulai hanya untuk fungsi-handler.

Seperti disebutkan di atas, kami telah memperoleh beberapa jenis penangan functor: objek functor langsung, ekspresi lambda, turunan dari kelas std :: function , fungsi individual. Dari jumlah tersebut, objek functor, ekspresi lambda, dan instance dari kelas fungsi std :: tidak dapat dibandingkan menggunakan operator == (mereka perlu dibandingkan di alamat), tetapi fungsi individual dapat, karena sudah disimpan di. Agar tidak menulis ulang fungsi perbandingan secara terpisah untuk setiap kasus, kami menulisnya dalam bentuk umum:

 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 ); } }; } // template<class TFunctor> class FunctorHolder { . . . using MyType = FunctorHolder<TFunctor>; public: bool operator==( const MyType& other ) const { return EqualityChecker<TFunctor>::isEquals( m_functor, other.m_functor ); } private: TFunctor& m_functor; . . . }; 

Dapat dipahami bahwa is_equatable adalah templat bantu yang menentukan apakah dua contoh dari jenis yang diberikan dapat diperiksa untuk kesetaraan. Dengan bantuannya, menggunakan std :: enable_if , kami memilih satu dari dua struktur EqualityChecker yang sebagian terspesialisasi , yang akan melakukan perbandingan: berdasarkan nilai atau alamat. Is_equatable diimplementasikan , bisa sebagai berikut:

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

Implementasi ini didasarkan pada mekanisme SFINAE , yang telah digunakan sebelumnya . Hanya di sini kita memeriksa keberadaan operator == untuk instance kelas yang diberikan.

Dengan cara sederhana ini, implementasi perbandingan fungsi-fungsi handler sudah siap.

Pengumpulan sampah


Bersikap lunak, saya juga ingin memasukkan judul yang keras.

Kami sedang mendekati final, dan inilah saatnya untuk menyingkirkan sejumlah besar objek yang dibuat yang tidak dikontrol oleh siapa pun.

Dengan setiap aksi dari suatu peristiwa dengan seorang pawang, dua objek dibuat: Pemegang , yang menyimpan bagian yang dapat dieksekusi dari pawang, dan EventHandlermenghubungkannya dengan acara tersebut. Jangan lupa bahwa jika ada upaya untuk menambahkan kembali pawang, tidak ada tambahan yang akan terjadi - dua objek "ditangguhkan di udara" (kecuali, tentu saja, Anda tidak memeriksa kasus ini secara terpisah setiap kali). Situasi lain: melepas pawang; dua objek baru juga dibuat untuk mencari yang sama (sama) dalam daftar event handler; penangan yang ditemukan dari daftar, tentu saja, dihapus (jika ada), dan yang sementara ini, dibuat untuk pencarian dan terdiri dari dua objek, sekali lagi "di udara". Secara umum, tidak keren.

Beralih ke pointer cerdas . Kita perlu menentukan semantik kepemilikan masing-masing dari dua objek handler nantinya: kepemilikan tunggal ( std :: unique_ptr ) atau dibagikan ( std :: shared_ptr ).

Pemegang, selain menggunakan acara itu sendiri ketika menambahkan / menghapusnya harus disimpan di EventHandler , oleh karena itu kami gunakan untuk kepemilikan bersama, dan untuk EventHandler itu adalah satu-satunya, karena setelah pembuatan, itu akan disimpan hanya dalam daftar penangan acara.

Kami menyadari ide ini:

 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 ...TParams> class IEvent { public: template<class TSome> bool operator+=( TSome&& some ) { return addHandler( HandlerCast<TSome>::cast<TParams...>( some ) ); } template<class TSome> bool operator-=( TSome&& some ) { return removeHandler( HandlerCast<TSome>::cast<TParams...>( some ) ); } protected: using TEventHandlerPtr = THandlerPtr<TParams...>; IEvent() {} virtual bool addHandler( TEventHandlerPtr eventHandler ) = 0; virtual bool removeHandler( TEventHandlerPtr eventHandler ) = 0; }; template<class ...TParams> class TEvent : public IEvent<TParams...> { using TEventHandlerIt = typename std::list<TEventHandlerPtr>::const_iterator; public: TEvent() { . . . } ~TEvent() { // empty } protected: virtual bool addHandler( TEventHandlerPtr 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( TEventHandlerPtr eventHandler ) override { . . . } private: //      'm_handlerListMutex' inline TEventHandlerIt findEventHandler( const TEventHandlerPtr& eventHandler ) const { return std::find_if( m_handlers.cbegin(), m_handlers.cend(), [ &eventHandler ]( const TEventHandlerPtr& oneHandler ) { return ( *oneHandler == *eventHandler ); } ); } //      'm_handlerListMutex' inline void deleteHandler( TEventHandlerIt it ) { m_handlers.erase( it ); } std::list<TEventHandlerPtr> m_handlers; . . . }; 

 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 ) 

Hal pertama yang pertama.

Untuk memulai, acara dan antarmuka untuk bekerja dengan penangan. Dalam yang terakhir, Anda tidak dapat lagi mengkonversi jenis secara langsung dengan menggunakan static_cast , karena jenis yang dikonversi terletak “di dalam” std :: shared_ptr . Sekarang, untuk konversi seperti itu, kita akan menggunakan struktur HandlerCast tambahan , yang, dengan spesialisasi pribadinya, akan memberikan akses ke objek di dalam std :: shared_ptr , dan sudah bekerja dengannya (dalam implementasi non-khusus), ia akan menerapkan static_cast lama yang baik .

Acara itu sendiri; Ada beberapa perubahan penting di sini juga. Pertama, kami akan berhenti secara manual menghapus instance handler di destructor dan ketika menghapus; sekarang cukup untuk menghapus pointer pintar dengan penangan ini dari daftar. Selain itu, ketika menambahkan handler, penting untuk tidak melupakan std :: move , karena std :: unique_ptr tidak mendukung penyalinan (yang cukup logis untuk semantik seperti itu).

Mari kita beralih ke penangan. Menurut tradisi lama, hanya satu yang diberikan, yang kedua serupa. Dan di sini, pada pandangan pertama, semuanya bermuara pada mengubah jenis objek yang disimpan / dibuat dari tautan / pointer ke pointer pintar.

Tetapi ada satu titik halus. Fungsi createMethodEventHandler akan mengembalikan std :: shared_ptr ke instanceMethodHolder . Beberapa saat kemudian, suatu usaha akan dilakukan untuk mengubahnya menjadi tipe handler ( MethodEventHandler ), di mana ia harus membuat instance baru dari MethodEventHandler , meneruskannya ke std :: shared_ptr constructor pada dirinya sendiri. Inilah yang dimaksudkan untuk instance MethodHolder untuk dihapus nanti ketika instance MethodEventHandler dihapus . Tetapi masalahnya adalah bahwa MethodHolder tidak memiliki akses ke std :: shared_ptr yang sudah dibuat yang menyimpannya sendiri.

Untuk mengatasi masalah, Anda harus menyimpan pointer pintar untuk diri sendiri di MethodHolder . Namun, agar tidak memengaruhi penghapusannya, kami gunakanstd :: lemah_ptr :

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

Untuk lebih jelasnya, saya akan memberikan perkiraan urutan kejadian saat menghapus pawang dari suatu acara (permintaan maaf saya untuk kata-kata acak):

  • peristiwa menghapus item dari daftar ( m_handlers.erase (it); ), yang menyebabkan destruktornya disebut;
  • destructor std :: unique_ptr dipanggil , yang mengarah ke panggilan ke destruktor objek yang dikelola;
  • destructor MethodEventHandler disebut , yang menghapus semua bidang objek, termasuk bidang m_methodHolder , yang std :: shared_ptr ;
  • std::shared_ptr ; , (.. ) ( MethodHolder ); , std::weak_ptr ;
  • MethodHolder , , , m_me , std::weak_ptr ;
  • std::weak_ptr ; ; .. std::weak_ptr , ;
  • .

Penting untuk diingat bahwa destruktor dari kelas AbstractEventHandler harus virtual; jika tidak, setelah klausa 2 pada klausa 3 , destruktor AbstractEventHandler akan dipanggil dan tindakan lebih lanjut tidak akan dilakukan.

Koneksi event dan handler


Dalam beberapa kasus, ketika menambahkan / menghapus satu handler dari suatu peristiwa cukup sering terjadi (berdasarkan beberapa logika), Anda tidak ingin repot, mendapatkan instance dari event tersebut dan instance dari handler setiap kali, untuk sekali lagi menerapkan berlangganan / berhenti berlangganan dari event ini. Tapi saya ingin menghubungkan mereka sekali, dan kemudian, jika perlu, bekerja dengan koneksi ini, menambahkan / menghapus dengan itu handler yang telah ditentukan dari acara yang telah ditentukan. Anda dapat menerapkan ini sebagai berikut:

 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: //      'm_handlerListMutex' inline TEventHandlerIt findEventHandler( const TEventHandlerPtr& eventHandler ) const { . . . } std::list<TEventHandlerPtr> m_handlers; mutable std::shared_mutex m_handlerListMutex; . . . }; 

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

Seperti yang Anda lihat, sekarang tempat lain yang memungkinkan untuk menyimpan instance dari handler telah ditambahkan, jadi kami akan menggunakan std :: shared_ptr alih-alih std :: unique_ptr untuk ini .

Namun, kelas ini, bagi saya, sedikit tidak nyaman untuk digunakan. Saya ingin menyimpan dan membuat instance koneksi tanpa daftar parameter yang instantiate template kelas.

Kami menerapkan ini menggunakan kelas leluhur abstrak dan pembungkus:

 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 adalah struktur pendukung yang sama yang digunakan di sini . Ngomong-ngomong, penting untuk tidak lupa membuat destruktor AbstractEventJoin virtual sehingga ketika Anda menghapus instansinya di destruktor EventJoinWrapper , destruktor HandlerEventJoin disebut , jika tidak bidang THandlerPtr dan, oleh karena itu, pawang itu sendiri tidak akan dihancurkan .

Implementasi ini tampaknya bisa diterapkan, tetapi hanya pada pandangan pertama. Menyalin atau memindahkan instance dari EventJoinWrapper akan menghapus m_eventJoin di destruktornya lagi . Oleh karena itu, kami menggunakan std :: shared_ptr untuk menyimpan instanceAbstractEventJoin , serta mengimplementasikan semantik gerakan yang sedikit dioptimalkan (dan menyalin), karena ini akan berpotensi operasi sering.

 class EventJoinWrapper { public: EventJoinWrapper( EventJoinWrapper&& other ) : m_eventJoin( std::move( other.m_eventJoin ) ) { } EventJoinWrapper( EventJoinWrapper& other ) : m_eventJoin( other.m_eventJoin ) { } ~EventJoinWrapper() { /*empty*/ } 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; }; 

Sekarang saat menghubungkan pawang ke suatu peristiwa, Anda dapat segera mengembalikan instance koneksi baru:

 template<class ...TParams> class IEvent { . . . public: template<class TSome> EventJoin operator+=( TSome&& some ) { EventJoin result( *this, std::forward<TSome>( some ) ); result.join(); return result; } . . . }; 

Dan setelah menyelesaikan ketergantungan segitiga dengan menyertakan (IEvent <= EventJointWrapper.hpp; EventJointWrapper <= HandlerEventJoin.hpp; HandlerEventJoin <= IEvent.hpp) dengan membagi beberapa file menjadi .h dan .hpp Anda bahkan dapat bekerja dengan ini.

Membuat instance koneksi mengikuti aturan yang sama yang berfungsi saat event handler berlangganan:

 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 ) ); // ok EventJoin join2 = EventJoin( _eventHolder.onEvent, METHOD_HANDLER( _methodsHolder, MethodsHolder::method2 ) ); // ok EventJoin join3 = EventJoin( _eventHolder.onEvent, METHOD_HANDLER( _methodsHolder, MethodsHolder::method3 ) ); // error EventJoin join4 = EventJoin( _eventHolder.onEvent, METHOD_HANDLER( _methodsHolder, MethodsHolder::method4 ) ); // error EventJoin join5 = EventJoin( _eventHolder.onEvent, METHOD_HANDLER( _methodsHolder, MethodsHolder::method5 ) ); // error return 0; } 

Plus, Anda dapat "mengaktifkan" / "menonaktifkan" pemrosesan acara (yang, pada prinsipnya, koneksi dibuat):

 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. 

Ringkasan


Pertama, perlu dicatat bahwa tugas menulis artikel sesingkat mungkin dan secara singkat gagal total.

Saya berharap implementasi dari pemrosesan acara yang dihasilkan cukup fungsional dan akan bermanfaat bagi setidaknya seseorang.

Contoh yang sangat rumit yang menunjukkan fitur utama
 #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; } 

Kesimpulan:

 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: 


Perlu dicatat beberapa poin penting:

  • itu tidak ditentukan secara terpisah, jadi saya akan menyebutkan bahwa acara itu sendiri dalam implementasi ini adalah functor, yang berarti dapat bertindak sebagai penangan untuk acara lain;
  • sekarang Anda tidak dapat menggunakan metode konstan (fungsi anggota kelas) sebagai penangan; Saya pikir jika kesempatan seperti itu diperlukan, tidak sulit untuk menulis jenis penangan baru untuk ini berdasarkan yang sudah ada.

Selain itu, dalam versi final ada beberapa poin yang dihilangkan dalam artikel untuk visibilitas dan keterbacaan kode yang lebih besar:

  • jenis nilai pengembalian metode (fungsi anggota kelas) untuk penangan yang sesuai dapat berupa apa saja, tidak harus batal (untuk penangan-fungsi, ini juga dilakukan)
  • seluruh implementasi dibungkus dengan namespaces untuk kemudahan penggunaan dalam proyek (jika ini tampaknya berlebihan bagi seseorang, Anda selalu dapat menghapusnya);
  • specifier noexcept telah ditambahkan di beberapa tempat .

Untuk semua yang telah membaca di sini setidaknya secara diagonal, busur rendah. Saya lampirkan semua kode; itu juga dapat diambil di sini (dengan semua perbaikan terbaru).

Seluruh kode
./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/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(); }; } // joins } // events 


./events/joins/abstracteventjoin.cpp
 #include "abstracteventjoin.h" namespace events { namespace joins { AbstractEventJoin::AbstractEventJoin() { } AbstractEventJoin::~AbstractEventJoin() { } } // joins } // events 


./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. Di sini dan sebelumnya dalam artikel, kode yang ditulis di bawah VC ++ 14 diberikan . Untuk kompatibilitas dengan kompiler lain, lebih baik mengambil kode dari tautan. Terima kasih khusus kepada Cheater karena memberikan kompatibilitas dengan GCC .
UPD2. Terima kasih lexxmark karena melihat ada lubang pengaman untuk beberapa panggilan acara secara bersamaan.
Perbaikan kecil
 namespace { template<class ...TParams> struct TypeHelper { using TEventHandlerPtr = handlers::TEventHandlerPtr<TParams...>; using TEventHandlerIt = typename std::list<TEventHandlerPtr>::const_iterator; }; } // template<class ...TParams> class IEvent { . . . protected: using TMyEventHandlerPtr = typename TypeHelper<TParams...>::TEventHandlerPtr; . . . }; namespace { template<class ...TParams> struct EventCore { using TMyHandlerPtr = typename TypeHelper<TParams...>::TEventHandlerPtr; std::list<TMyHandlerPtr> handlers; mutable std::shared_mutex coreMutex; }; template<class ...TParams> class HandlerRunner { using TMyEventCore = EventCore<TParams...>; using TMyHandlerIt = typename TypeHelper<TParams...>::TEventHandlerIt; public: HandlerRunner( TMyEventCore& eventCore ) : m_eventCore( eventCore ), currentIt(), wasRemoving( false ) { } void run( TParams... params ) { m_eventCore.coreMutex.lock_shared(); currentIt = m_eventCore.handlers.begin(); wasRemoving = false; while( currentIt != m_eventCore.handlers.end() ) { m_eventCore.coreMutex.unlock_shared(); ( *currentIt )->call( params... ); m_eventCore.coreMutex.lock_shared(); if( wasRemoving ) wasRemoving = false; else ++currentIt; } m_eventCore.coreMutex.unlock_shared(); } TMyHandlerIt currentIt; mutable bool wasRemoving; private: TMyEventCore& m_eventCore; }; } // template<class ...TParams> class TEvent : public IEvent<TParams...> { using TMyEventHandlerPtr = typename TypeHelper<TParams...>::TEventHandlerPtr; using TMyEventHandlerIt = typename TypeHelper<TParams...>::TEventHandlerIt; using TMyHandlerRunner = HandlerRunner<TParams...>; public: TEvent() : m_core() { } void operator()( TParams... params ) { TMyHandlerRunner newHandlerRunner( m_core ); m_core.coreMutex.lock_shared(); auto it = m_handlerRunners.insert( m_handlerRunners.end(), &newHandlerRunner ); m_core.coreMutex.unlock_shared(); newHandlerRunner.run( params... ); m_core.coreMutex.lock_shared(); m_handlerRunners.erase( it ); m_core.coreMutex.unlock_shared(); } protected: virtual bool isHandlerAdded( const TMyEventHandlerPtr& eventHandler ) const override { std::shared_lock<std::shared_mutex> _coreMutexLock( m_core.coreMutex ); return ( findEventHandler( eventHandler ) != m_core.handlers.end() ); } virtual bool addHandler( TMyEventHandlerPtr eventHandler ) override { std::unique_lock<std::shared_mutex> _coreMutexLock( m_core.coreMutex ); if( findEventHandler( eventHandler ) == m_core.handlers.end() ) { m_core.handlers.push_back( std::move( eventHandler ) ); return true; } return false; } virtual bool removeHandler( TMyEventHandlerPtr eventHandler ) override { std::unique_lock<std::shared_mutex> _coreMutexLock( m_core.coreMutex ); auto it = findEventHandler( eventHandler ); if( it != m_core.handlers.end() ) { for( TMyHandlerRunner* oneHandlerRunner : m_handlerRunners ) { if( it == oneHandlerRunner->currentIt ) { ++oneHandlerRunner->currentIt; oneHandlerRunner->wasRemoving = true; } } m_core.handlers.erase( it ); return true; } return false; } private: //      'm_core.coreMutex' inline TMyEventHandlerIt findEventHandler( const TMyEventHandlerPtr& eventHandler ) const { return std::find_if( m_core.handlers.cbegin(), m_core.handlers.cend(), [ &eventHandler ]( const TMyEventHandlerPtr& oneHandler ) { return ( *oneHandler == *eventHandler ); } ); } EventCore<TParams...> m_core; std::list<TMyHandlerRunner*> m_handlerRunners; }; 

(, , ) HandlerRunner , . , : currentIt ( ) wasRemoving (, ). HandlerRunner' operator() ; (, ) , EventCore . T.O. , , , , , .
UPD3. Berkat isnullxbh , kesalahan lain ditemukan. Hal ini terhubung dengan perlakuan yang salah dan penahan berikutnya benda ditransfer melalui nilai p (terutama ekspresi lambda).
Koreksi
, 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 TMethodHolder, class ...TParams> class MethodEventHandler : public AbstractEventHandler<TParams...> { public: virtual void call( TParams... params ) override { static_assert( IsMethodParamsCompatible<TMethodHolder, TParams...>::value, "Event and method arguments are not compatible" ); ( m_methodHolder->m_innerHolder.get().*m_methodHolder->m_method )( params... ); } private: TMethodHolderPtr m_methodHolder; . . . }; 

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

Selesai FunctorHolder . . - .

PS Perbandingan dengan mekanisme sinyal / slot Qt


Saya pikir saya tidak akan salah jika saya mengatakan bahwa Qt adalah kerangka kerja yang sangat umum untuk berkembang di C ++ . Antara lain, ia juga memiliki mekanisme pemrosesan peristiwa sendiri , di mana ada sinyal sebagai analog peristiwa dan slot sebagai analog penangan. Ini diimplementasikan menggunakan Meta-Object Compiler , yang merupakan bagian dari Meta-Object System yang lebih global , yang, pada gilirannya, diimplementasikan menggunakan add-on yang digunakan dalam Qt lebih dari C ++ .

Fitur kedua implementasi:


  • kemampuan untuk menghubungkan sinyal (peristiwa) ke metode (fungsi anggota), fungsi dan fungsi;
  • () (), ( lvalue , rvalue );
  • ( );
  • () ( ).

Qt :



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 ;
  • penggunaan kode tambahan yang dihasilkan oleh moc ;
    ini sudah sepenuhnya subjektif, tetapi keputusannya adalah, di mana untuk setiap kelas yang menggunakan sinyal dan slot (slot tidak selalu) ada beberapa (berdasarkan file untuk setiap konfigurasi) file yang dihasilkan yang menyebabkan beberapa ketidaknyamanan; tapi jujur ​​saja, ini adalah kekurangan yang paling kecil.

Penting untuk dicatat bahwa perbandingan dengan Qt ini sangat subyektif dan tidak bertujuan meninggikan atau mengutuk kerangka kerja ini. Harus diingat bahwa selain mekanisme sinyal / slot, Qt memberikan fungsionalitas yang hebat, baik menggunakan mekanisme ini dan tidak bergantung padanya. Bagaimanapun, selalu terserah Anda untuk memutuskan apa yang akan digunakan dan apa yang tidak.

Source: https://habr.com/ru/post/id424593/


All Articles