参考文章: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是否被析构。

在C++11中实现监听者模式的更多相关文章

  1. Java Listener pattern 监听者模式

    Java Listener pattern 监听者模式 2016-5-12 监听者模式(观察者模式)能降低对象之间耦合程度.为两个相互依赖调用的类进行解耦. 便于进行模块化开发工作.不同模块的开发者可 ...

  2. IOS中的几中观察监听模式

    本文介绍Objective C中实现观察者模式(也被称为广播者/监听者.发布/注册或者通知)的五种方法以及每种方法的价值所在. 该文章将包括: 1 手动广播者和监听者(Broadcaster and ...

  3. iOS监听模式系列之IOS中的几中观察监听模式

    本文介绍Objective C中实现观察者模式(也被称为广播者/监听者.发布/注册或者通知)的五种方法以及每种方法的价值所在. 该文章将包括: 1 手动广播者和监听者(Broadcaster and ...

  4. Web应用中监听者的通知顺序按照DD中的定义顺序

    Web应用中监听者的通知顺序按照DD中的定义顺序: XML: <?xml version="1.0" encoding="UTF-8"?> < ...

  5. Javaweb上下文监听者ServletContextListener

    一个监听类,不是一个servlet或JSP,它能监听ServletContext一生中的两个关键事件:初始化(创建)和撤销.这个类实现了javax.servlet.ServletContextList ...

  6. 在java.util中有EventListener接口:所有事件监听者都要实现这个接口。

    在java.util中有EventListener接口:所有事件监听者都要实现这个接口. java.util中有EventObject类:所有的事件都为其子类.   事件范例在\CoreJava\Gi ...

  7. Java监听模式

    说明 生活中,监听无处不在.比如说,手机播放音乐功能,也是一种监听:你不点击播放按钮,手机就不放歌,当你点击时,手机就播放音乐.即触发某种行为,便执行相应的动作. 组成 Java监听模式右三个部分组成 ...

  8. EntityFramework之监听者判断SQL性能指标

    前言 当我们利用EF这个ORM框架时,我们可能会利用LINQ或者原生的SQL语句来进行数据操作,此时我们无法确定我们的代码是否会给数据库带来一定的负载,当给数据库带来一定的压力时,由于项目中对数据进行 ...

  9. 从linux0.11中起动部分代码看汇编调用c语言函数

    上一篇分析了c语言的函数调用栈情况,知道了c语言的函数调用机制后,我们来看一下,linux0.11中起动部分的代码是如何从汇编跳入c语言函数的.在LINUX 0.11中的head.s文件中会看到如下一 ...

随机推荐

  1. Scrum团队成立及《构建之法》第六、七章读后感

    5.Scrum团队成立 5.1 团队名称:喳喳      团队目标:突破渣渣      团队口号:吱吱喳喳      团队照: 5.2 角色分配 产品负责人: 112冯婉莹 Scrum Master: ...

  2. PAT 甲级 1041 Be Unique

    https://pintia.cn/problem-sets/994805342720868352/problems/994805444361437184 Being unique is so imp ...

  3. 【转】Apache httpd.conf配置解释

    转自:http://jafy00.blog.51cto.com/2594646/501373 常用配置指令说明 1. ServerRoot:服务器的基础目录,一般来说它将包含conf/和logs/子目 ...

  4. Halcon 笔记3 形态学

    Halcon 三大数据类型: (1)图像 (2)区域 (3)XLD  查看时间工具 如果想让图像减少,则进行腐蚀(或者使用开运算),反之,则进行膨胀(或闭运算) 腐蚀后再进行膨胀,相当于进行开运算.因 ...

  5. JSP 问题总结

    <input type="button" value="返回" onclick="javascript:window.location.href ...

  6. DNS原理及解析过程

    本文主要参考自:http://369369.blog.51cto.com/319630/812889 并做了小幅修改 什么是DNS? 因特网上的主机和人类一样,也可以使用多种方式进行识别.主机的一种识 ...

  7. 【数据库】百万级数据库SQL优化大总结

    网上关于SQL优化的教程很多,但是比较杂乱.近日有空整理了一下,写出来跟大家分享一下,其中有错误和不足的地方,还请大家纠正补充. 这篇文章我花费了大量的时间查找资料.修改.排版,希望大家阅读之后,感觉 ...

  8. SWERC2015-I Text Processor

    题意 给一个长度为\(n\)的字符串\(s\),再给定一个\(w\),问对于所有的\(i\in [1,n-w+1]\),\(s[i..i+w-1]\)有多少个不同字串.\(n,w\le 10^5\). ...

  9. (转)java web自定义分页标签

    转载至http://liuxi1024.iteye.com/blog/707784 效果如图: 1.JSP规范1.1版本后增加了自定义标签库.实现自定义标签的步骤 (1)开发自定义标签处理类. (2) ...

  10. 【BZOJ4709】柠檬(动态规划,单调栈)

    [BZOJ4709]柠檬(动态规划,单调栈) 题面 BZOJ 题解 从左取和从右取没有区别,本质上就是要分段. 设\(f[i]\)表示前\(i\)个位置的最大值. 那么相当于我们枚举一个前面的位置\( ...