zoukankan      html  css  js  c++  java
  • 在C++11中实现监听者模式

    参考文章:https://coderwall.com/p/u4w9ra/implementing-signals-in-c-11

    最近在完成C++大作业时,碰到了监听者模式的需求。

    尽管C++下也可以通过声明IObserver这样的接口,做继承,然后实现类似Java中的监听者模式。

    但是这种方法并不是最适合C++的。通过利用C++11中的函数对象和RAII,我们可以实现一个更符合C++国情的监听者模式。

    代码如下:

    
    	/* Signal class for implementing event. */
    	template <typename... TFuncArgs>
    	class Signal {
    	public:
    		using Callback = std::function<void(TFuncArgs...)>;
    
    		/* Connection class.
    		Disconnect() will be called automatically once it's out of scope.
    		*/
    		class SignalConnection {
    		private:
    			friend class Signal;
    			/* We only allow class Signal to create a connection. */
    			SignalConnection(Signal& signal, int id) noexcept:id(id), signal(signal) {}
    		public:
    			/* A copy constructor of "connection" is really confusing. just delete it. */
    			SignalConnection(const SignalConnection& copy) = delete;
    			/* without a copy constructor, we can't return SignalConnection, unless we provide a move constructor. */
    			SignalConnection(SignalConnection&& toMove) noexcept : id(toMove.id), signal(toMove.signal),disconnected(toMove.disconnected) {}
    			~SignalConnection() {
    				Disconnect();
    			}
    			int id;
    			Signal& signal;
    			bool disconnected = false;
    			void Disconnect() {
    				if (disconnected)
    					return;
    				disconnected = true;
    				signal.Disconnect(*this);
    			}
    		};
    
    		/* <b>Register to the Signal.</b>
    		Returns a connection object.
    		the connection object will automatically disconnect once it's out of scope.*/
    		SignalConnection Connect(Callback callback) {
    			callbacks.push_back(std::pair<int, Callback>(idRoller++, callback));
    			return SignalConnection(*this, idRoller - 1);
    		}
    
    		void Invoke(TFuncArgs... args) {
    			for (auto& con : callbacks) {
    				(con.second)(args...);
    			}
    		}
    
    		void operator()(TFuncArgs... args) {
    			Invoke(args...);
    		}
    
    	private:
    		/* ID Counter.
    		We look for the connection's corresponding callback using id, since the operator== of std::function doesn't work as imagine.
    		*/
    		int idRoller = 0;
    		std::list<std::pair<int, Callback>> callbacks;
    
    		void Disconnect(SignalConnection& t) {
    			callbacks.erase(std::remove_if(callbacks.begin(), callbacks.end(), [&](auto& pCallback) {
    				return pCallback.first == t.id;
    			}), callbacks.end());
    		}
    	};
    

    一共两个类,Signal类表示事件,SignalConnection是由Signal返回给监听者的一个句柄,用于取消监听。

    SignalConnection在析构函数中会自动调用进行取消监听,这样监听者不用担心内存泄露的问题。

    Signal中保存的回调是pair<int,Callback>的结构。因为std::function对象不能用==直接进行比较,因此我们需要对每个监听者分配一个独一无二的id,在取消监听时,通过比较这个id,来确定删除哪个监听者。

    为了避免歧义,我们将SignalConnection类的复制构造函数设置为delete,但是允许move构造,不然Signal也无法在函数里返回一个SignalConnection了。

    使用方法:

    void TestFunc1(int t) {
    	cout << "Func1" << endl;
    }
    void TestFunc2(int t,string someArg) {
    	cout << "Func2" << endl;
    }
    
    int main(){
    	using TestDelegate = Signal<int>;	
    	TestDelegate testEvent;
    	TestDelegate::SignalConnection testCon1 = testEvent.Connect(&TestFunc1);    
    	TestDelegate::SignalConnection testCon2 = testEvent.Connect(std::bind(TestFunc2 ,std::placeholders::_1, "arg"));  //用bind去绑定参数
    	auto testCon3 = testEvent.Connect(&TestFunc1);        //用auto简化声明
    	testEvent(1);
    ...
    

    可以看到使用起来非常简洁自然。

    上面这个实现有一定的缺陷,就是当Signal被析构后,没办法通知SignalConnection去Disconnect。在这之后此时SignalConnection被析构的话,调用signal.Disconnect()会导致引用错误。

    最先想到的改进方法是让SignalConnection在Disconnect之前检查Signal还在不在。

    但是既然都已经析构了,我们是没有办法去检查的。除非我们让Signal在最开始构造的时候,必须在堆上构造,然后用shared_ptr保存。让SignalConnection保存一个weak_ptr去检查Signal是否被析构。实际情况下这种实现多少有点不美观。

    第二种方法,就是尝试在Signal析构时,将连接到它的SignalConnection全部Disconnect。

    这种方法,我们需要在Signal中保存SignalConnection的指针。在Connect时,我们不再返回SignalConnection的对象,而是SignalConnection的shared_ptr,同时保存一个对应的weak_ptr。这种方法相对来说要简洁一点,只是将Connect的返回类型改为了shared_ptr,其他特性都得到了保留。

    	/* Signal class for implementing event. */
    	template <typename... TFuncArgs>
    	class Signal {
    	public:
    		using Callback = std::function<void(TFuncArgs...)>;
    
    		/* Connection class.
    		Disconnect() will be called automatically once it's out of scope.
    		*/
    		class SignalConnection {
    		private:
    			friend class Signal;
    			/* We only allow class Signal to create a connection. */
    			SignalConnection(Signal& signal) noexcept: signal(signal) {}
    		public:
    			/* A copy constructor of "connection" is really confusing. just delete it. */
    			SignalConnection(const SignalConnection& copy) = delete;
    			/* without a copy constructor, we can't return SignalConnection, unless we provide a move constructor. */
    			SignalConnection(SignalConnection&& toMove) noexcept : id(toMove.id), signal(toMove.signal),disconnected(toMove.disconnected) {}
    			~SignalConnection() {
    				Disconnect();
    			}
    			Signal& signal;
    			bool disconnected = false;
    			void Disconnect() {
    				if (disconnected)
    					return;
    				disconnected = true;
    				signal.Disconnect(this);
    			}
    		};
    		using SPConnection = std::shared_ptr<SignalConnection>;
    
    		/* Register to the Signal.
    		Returns a connection object.
    		the connection object will automatically disconnect once it's out of scope.*/
    		SPConnection Connect(Callback callback) {
    			auto t = std::shared_ptr<SignalConnection>(new SignalConnection(*this));
    			callbacks.push_back(std::pair<std::weak_ptr<SignalConnection>, Callback>(t, callback));
    			return t;
    		}
    
    		void Invoke(TFuncArgs... args) {
    			for (auto& con : callbacks) {
    				(con.second)(args...);
    			}
    		}
    
    		void operator()(TFuncArgs... args) {
    			Invoke(args...);
    		}
    
    		~Signal() {
    			for (auto& t : callbacks) {
    				if (auto sp = t.first.lock()) {	//translate to shared_ptr
    					sp->disconnected = true;
    				}
    			}
    		}
    
    	private:
    		std::list<std::pair<std::weak_ptr<SignalConnection>, Callback>> callbacks;
    
    		void Disconnect(SignalConnection* t) {
    			callbacks.erase(std::remove_if(callbacks.begin(), callbacks.end(), [&](auto& pCallback) {
    				auto ptr = pCallback.first.lock();
    				return !ptr || ptr.get() == t;
    			}), callbacks.end());
    		}
    	};
    

    最后一个办法,我们不用修改任何函数签名就可以解决这个问题。

    解决这个问题的关键是让SignalConnection得知Signal是否被析构。所以我们要在不受析构影响的堆内存中找一个地方存放这个信息,让Signal被析构的时候在这个地方表示自己已经被析构。然后在构造SignalConnection时,将这个内存地址一同传入SignalConnection。SignalConnection去检查这个内存就能知道Signal是否被析构了。为了防止内存泄露,最后一个检查的SignalConnection还需要把这块内存回收。

    这其实就类似于引用计数了,而我们可以用智能指针去模拟这些行为,而不用自己真的去管理内存。代码如下。

    	/* Signal class for implementing event. */
    	template <typename... TFuncArgs>
    	class Signal {
    	public:
    		using Callback = std::function<void(TFuncArgs...)>;
    		std::shared_ptr<int> survivePtr;
    		Signal() : survivePtr(make_shared<int>(0)) {
    		}
    		/* Connection class.
    		Disconnect() will be called automatically once it's out of scope.
    		*/
    		class SignalConnection {
    		private:
    			friend class Signal;
    			/* We only allow class Signal to create a connection. */
    			SignalConnection(Signal& signal, int id, std::shared_ptr<int> survivePtr) noexcept:id(id), signal(signal), signalSurvivePtr(survivePtr){}
    		public:
    			/* A copy constructor of "connection" is really confusing. just delete it. */
    			SignalConnection(const SignalConnection& copy) = delete;
    			/* without a copy constructor, we can't return SignalConnection, unless we provide a move constructor. */
    			SignalConnection(SignalConnection&& toMove) noexcept : id(toMove.id), signal(toMove.signal), disconnected(toMove.disconnected), signalSurvivePtr(toMove.signalSurvivePtr) {}
    			~SignalConnection() {
    				Disconnect();
    			}
    			int id;
    			Signal& signal;
    			bool disconnected = false;
    			std::weak_ptr<int> signalSurvivePtr;
    			void Disconnect() {
    				if (disconnected || signalSurvivePtr.expired())
    					return;
    				disconnected = true;
    				signal.Disconnect(*this);
    			}
    		};    
    
    		/* <b>Register to the Signal.</b>
    		Returns a connection object.
    		the connection object will automatically disconnect once it's out of scope.*/
    		SignalConnection Connect(Callback callback) {
    			callbacks.push_back(std::pair<int, Callback>(idRoller++, callback));
    			return SignalConnection(*this, idRoller - 1, survivePtr);
    		}
    
    		void Invoke(TFuncArgs... args) {
    			for (auto& con : callbacks) {
    				(con.second)(args...);
    			}
    		}
    
    		void operator()(TFuncArgs... args) {
    			Invoke(args...);
    		}
    
    	private:
    		/* ID Counter.
    		We look for the connection's corresponding callback using id, since the operator== of std::function doesn't work as imagine.
    		*/
    		int idRoller = 0;
    		std::list<std::pair<int, Callback>> callbacks;
    
    		void Disconnect(SignalConnection& t) {
    			callbacks.erase(std::remove_if(callbacks.begin(), callbacks.end(), [&](auto& pCallback) {
    				return pCallback.first == t.id;
    			}), callbacks.end());
    		}
    	};
    

    Signal中我们保存一个shared_ptr,在SignalConnection中保存的是weak_ptr。这样当Signal被析构时,该内存空间的引用计数归零,此时SignalConnection可以通过自己的weak_ptr得知Signal是否被析构。

  • 相关阅读:
    Swing中的并发使用SwingWorker线程模式
    使用批处理打二次开发包
    【转】批处理删除SVN文件
    EAS BOS ORMapping的研究
    EAS BOS ORMapping 元数据解析示例
    ORMapping学习
    EAS BOS数据查询默认会查分录的分析
    【Oracle】常用查询
    KDTable公式解析提示信息设置
    【转】Swing多线程编程
  • 原文地址:https://www.cnblogs.com/yangrouchuan/p/7817773.html
Copyright © 2011-2022 走看看