引入
对象与对象之间的通信有多个方式,如果我们要提供一种对象之间的通信机制。这种机制,要能够给两个不同对象中的函数建立映射关系,前者被调用时后者也能被自动调用。
再深入一些,两个对象如果都互相不知道对方的存在,仍然可以建立联系。甚至一对一的映射可以扩展到多对多,具体对象之间的映射可以扩展到抽象概念之间。这样子如何实现呢?
观察者模式
在我之前就介绍过设计模式之观察者模式,它就可以实现上述引入所讲的,两个对象如果都互相不知道对方的存在,仍然可以建立联系。归结到底就是回调函数+映射表的方式实现该思想。
qt的信号-槽
信号-槽 是Qt自定义的一种通信机制,它不同于标准C/C++ 语言,它的本质就是一种观察者模式的具体实现
信号-槽的使用方法,是在普通的函数声明之前,加上signal、slot标记,然后通过connect函数把信号与槽 连接起来。后续只要调用 信号函数,就可以触发连接好的信号或槽函数。
连接的时候,前面的是发送者,后面的是接收者。信号与信号也可以连接,这种情况把接收者信号看做槽即可。
信号与槽的分类
信号-槽要分成两种来看待,一种是同一个线程内的信号-槽,另一种是跨线程的信号-槽。
-
同一个线程内的信号-槽,就相当于函数调用,和前面的观察者模式相似,只不过信号-槽稍微有些性能损耗(因为需要查找映射表,这也是观察者解耦的代价)。
-
跨线程的信号-槽,在信号触发时,发送者线程将槽函数的调用转化成了一次“调用事件”,放入事件循环中。接收者线程执行到下一次事件处理时,处理“调用事件”,调用相应的函数。
信号与槽的实现 - 元对象编译器moc
信号-槽的实现,借助一个工具:元对象编译器MOC(Meta Object Compiler)。
这个工具被集成在了Qt的编译工具链qmake中,在开始编译Qt工程时,会先去执行MOC,从代码中解析signals、slot、emit等等这些标准C/C++不存在的关键字,以及处理Q_OBJECT、Q_PROPERTY、Q_INVOKABLE等相关的宏,生成一个moc_xxx.cpp的C++文件。比如信号函数只要声明、不需要自己写实现,就是在这个moc_xxx.cpp文件中,自动生成的。MOC之后就是常规的C/C++编译、链接流程了。
moc的本质-反射机制
MOC的本质,其实是一个反射器。标准C++没有反射功能(将来会有),所以Qt用moc实现了反射功能。
什么叫反射呢? 简单来说,就是运行过程中,获取对象的构造函数、成员函数、成员变量。
举个例子来说明,有下面这样一个类声明:
class Tom {
public:
Tom() {}
const std::string & getName() const
{
return m_name;
}
void setName(const std::string &name)
{
m_name = name;
}
private:
std::string m_name;
};
类的使用者,看不到类的声明,头文件都拿不到,不能直接调用类的构造函数、成员函数。
从配置文件拿到了一段字符串“Tom”,就要创建一个Tom类的对象实例。然后又拿到一段“setName”的字符串,就要去调用Tom的setName函数。
面对这种需求,就需要把Tom类的构造函数、成员函数等信息存储起来,还要能够被调用到。
这些信息就是 “元信息”,使用者通过“元信息”就可以“使用这个类”。这便是反射了。
设计模式中的“工厂模式”,就是一个典型的反射案例。不过工厂模式只解决了构造函数的调用,没有成员函数、成员变量等信息。
参考
学习反射看这一篇就够了
如何优雅的实现C++编译期静态反射