Qt高级——Qt信号槽机制源码解析
基于Qt4.8.6版本
一、信号槽机制的原理
1、信号槽简介
信号槽是观察者模式的一种实现,特性如下:
A、一个信号就是一个能够被观察的事件,或者至少是事件已经发生的一种通知;
B、一个槽就是一个观察者,通常就是在被观察的对象发生改变的时候——也可以说是信号发出的时候——被调用的函数;
C、信号与槽的连接,形成一种观察者-被观察者的关系;
D、当事件或者状态发生改变的时候,信号就会被发出;同时,信号发出者有义务调用所有注册的对这个事件(信号)感兴趣的函数(槽)。
信号和槽是多对多的关系。一个信号可以连接多个槽,而一个槽也可以监听多个信号。
信号槽与语言无关,有多种方法可以实现信号槽,不同的实现机制会导致信号槽的差别很大。信号槽术语最初来自 Trolltech 公司的 Qt 库,由于其设计理念的先进性,立刻引起计算机科学界的注意,提出了多种不同的实现。目前,信号槽依然是 Qt 库的核心之一,其他许多库也提供了类似的实现,甚至出现了一些专门提供这一机制的工具库。
信号槽是Qt对象以及其派生类对象之间的一种高效通信接口,是Qt的核心特性,也是Qt区别与其他工具包的重要地方。信号槽完全独立于标准的C/C++语言,因此要正确的处理好信号和槽,必须借助于一个成为MOC(Meta Object Compiler)的Qt工具,MOC工具是一个C++预处理程序,能为高层次的事件处理自动生成所需要的附加代码。
成都创新互联公司是一家专业提供札达企业网站建设,专注与成都网站设计、网站建设、H5响应式网站、小程序制作等业务。10年已为札达众多企业、政府机构等服务。创新互联专业网站制作公司优惠进行中。
2、不同平台的实现
MFC中的消息机制没有采用C++中的虚函数机制,原因是消息太多,虚函数开销太大。在Qt中也没有采用C++中的虚函数机制,而是采用了信号槽机制,原因与此相同。更深层次的原因上,多态的底层实现机制只有两种,一种是按照名称查表,一种是按照位置查表。两种方式各有利弊,而C++的虚函数机制无条件的采用了后者,导致的问题就是在子类很少重载基类实现的时候开销太大,再加上界面编程中子类众多的情况,基本上C++的虚函数机制效率太低,于是各家库的编写者就只好自谋生路,当然,这其实是C++语言本身的缺陷。
二、Qt信号槽实例解析
1、信号槽使用示例
使用简单的实例:
#ifndef OBJECT_H
#define OBJECT_H
#include
#include
#include
class Object : public QObject
{
Q_OBJECT
Q_PROPERTY(int age READ age WRITE setAge NOTIFY ageChanged)
Q_PROPERTY(int score READ score WRITE setScore NOTIFY scoreChanged)
Q_PROPERTY(Level level READ level WRITE setLevel)
Q_CLASSINFO("Author", "Scorpio")
Q_CLASSINFO("Version", "1.0")
public:
enum Level
{
Basic = 1,
Middle,
Advanced,
Master
};
Q_ENUMS(Level)
protected:
QString m_name;
Level m_level;
int m_age;
int m_score;
void setLevel(const int& score)
{
if(score <= 60)
{
m_level = Basic;
}
else if(score < 100)
{
m_level = Middle;
}
else if(score < 150)
{
m_level = Advanced;
}
else
{
m_level = Master;
}
}
public:
explicit Object(QString name, QObject *parent = 0):QObject(parent)
{
m_name = name;
setObjectName(m_name);
connect(this, SIGNAL(ageChanged(int)), this, SLOT(onAgeChanged(int)));
connect(this, SIGNAL(scoreChanged(int)), this, SLOT(onScoreChanged(int)));
}
int age()const
{
return m_age;
}
void setAge(const int& age)
{
m_age = age;
emit ageChanged(m_age);
}
int score()const
{
return m_score;
}
void setScore(const int& score)
{
m_score = score;
setLevel(m_score);
emit scoreChanged(m_score);
}
Level level()const
{
return m_level;
}
void setLevel(const Level& level)
{
m_level = level;
}
signals:
void ageChanged(int age);
void scoreChanged(int score);
public slots:
void onAgeChanged(int age)
{
qDebug() << "age changed:" << age;
}
void onScoreChanged(int score)
{
qDebug() << "score changed:" << score;
}
};
#endif // OBJECT_H
Main函数:
#include
#include "Object.h"
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
Object ob("object");
//设置属性age
ob.setProperty("age", QVariant(30));
qDebug() << "age: " << ob.age();
qDebug() << "property age: " << ob.property("age").toInt();
//设置属性score
ob.setProperty("score", QVariant(90));
qDebug() << "score: " << ob.score();
qDebug() << "property score: " << ob.property("score").toInt();
qDebug() << "Level: " << ob.level();
ob.setProperty("level", 4);
qDebug() << "level: " << ob.level();
qDebug() << "Property level: " << ob.property("level").toInt();
//内省intropection,运行时查询对象信息
qDebug() << "object name: " << ob.objectName();
qDebug() << "class name: " << ob.metaObject()->className();
qDebug() << "isWidgetType: " << ob.isWidgetType();
qDebug() << "inherit: " << ob.inherits("QObject");
return a.exec();
}
2、SIGNAL与SLOT宏
SIGNAL与SLOT宏定义在/src/corelib/kernel/Qobjectdefs.h文件中。
Q_CORE_EXPORT const char *qFlagLocation(const char *method);
#define QTOSTRING_HELPER(s) #s
#define QTOSTRING(s) QTOSTRING_HELPER(s)
#ifndef QT_NO_DEBUG
# define QLOCATION "\0" __FILE__ ":" QTOSTRING(__LINE__)
# ifndef QT_NO_KEYWORDS
# define METHOD(a) qFlagLocation("0"#a QLOCATION)
# endif
# define SLOT(a) qFlagLocation("1"#a QLOCATION)
# define SIGNAL(a) qFlagLocation("2"#a QLOCATION)
#else
# ifndef QT_NO_KEYWORDS
# define METHOD(a) "0"#a
# endif
# define SLOT(a) "1"#a
# define SIGNAL(a) "2"#a
#endif
SIGNAL与SLOT宏会利用预编译器将一些参数转化成字符串,并且在前面添加上编码。
在调试模式中,如果signal的连接出现问题,提示警告信息的时候还会注明对应的文件位置。qFlagLocation 用于定位代码对应的行信息,会将对应代码的地址信息注册到一个有两个入口的表里。
Object.h文件中有关SIGNAL与SLOT宏部分代码如下:
connect(this, SIGNAL(ageChanged(int)), this, SLOT(onAgeChanged(int)));
connect(this, SIGNAL(scoreChanged(int)), this, SLOT(onScoreChanged(int)));
通过对Object.h文件进行预编译,得到Object.i文件。
使用G++进行预编译:
g++ -E Object.h -o Object.i -I/usr/local/Trolltech/Qt-4.8.6/include/QtCore -I/usr/local/Trolltech/Qt-4.8.6/include -I.
Object.i文件中结果如下:
connect(this, qFlagLocation("2""ageChanged(int)" "\0" "Object.h" ":" "54"), this, qFlagLocation("1""onAgeChanged(int)" "\0" "Object.h" ":" "54"));
connect(this, qFlagLocation("2""scoreChanged(int)" "\0" "Object.h" ":" "55"), this, qFlagLocation("1""onScoreChanged(int)" "\0" "Object.h" ":" "55"));
3、类的元对象
程序编译时make调用MOC对工程源码进行解析,生成相应类的moc_xxx.cpp文件,
const QMetaObject Object::staticMetaObject = {
{ &QObject::staticMetaObject, qt_meta_stringdata_Object,
qt_meta_data_Object, &staticMetaObjectExtraData }
};
静态成员staticMetaObject被填充的值如下:
const QMetaObject superdata;//元数据代表的类的基类的元数据,被填充为基类的元数据指针&QWidget::staticMetaObject
const char stringdata;//元数据的签名标记,被填充为qt_meta_stringdata_Widget.data
const uint *data;//元数据的索引数组的指针,被填充为qt_meta_data_Widget
const QMetaObject **extradata;//扩展元数据表的指针,内部被填充为函数指针qt_static_metacall。
staticMetaObjectExtraData初始化如下:
const QMetaObjectExtraData Object::staticMetaObjectExtraData = {
0, qt_static_metacall
};
QMetaObjectExtraData类型的内部成员static_metacall是一个指向Object::qt_static_metacall 的函数指针。
Object的内存布局如下:

Object内存布局已经包含了静态成员staticMetaObject和
staticMetaObjectExtraData成员。
const QMetaObject *Object::metaObject() const
{
return QObject::d_ptr->metaObject ? QObject::d_ptr->metaObject : &staticMetaObject;
}
QObject::d_ptr->metaObject仅供动态元对象(QML对象)使用,所以一般而言,虚函数 metaObject() 仅返回类的 staticMetaObject。
4、元数据表
Qt程序编译时make会调用MOC工具对源文件进行分析,如果某个类包含了Q_OBJECT宏,MOC会生成对应的moc_xxx.cpp文件。
moc_Object.cpp文件内容中:
Object的元数据如下:
static const uint qt_meta_data_Object[] = {
// content:内容信息
6, // revision MOC生成代码的版本号
0, // classname 类名,在qt_meta_stringdata_Object数组中索引为0
2, 14, // classinfo 类信息,有2个cassinfo定义,
4, 18, // methods 类有4个自定义方法,即信号与槽个数,
3, 38, // properties 属性的位置信息,有3个自定义属性,
1, 50, // enums/sets 枚举的位置信息,有一个自定义枚举,在qt_meta_stringdata_Object数组中索引为50
0, 0, // constructors 构造函数的位置信息
0, // flags
2, // signalCount
// classinfo: key, value //类信息的存储在qt_meta_stringdata_Object数组中,
15, 7, //第一个类信息,key的数组索引为15,即Author,value的数组索引为7,即Scorpio
26, 22, //第二个类信息,key的数组索引为26,即Version,value的数组索引为22,即1.0
// signals: signature, parameters, type, tag, flags
39, 35, 34, 34, 0x05, //第一个自定义信号的签名存储在qt_meta_stringdata_Object数组中,
//索引是39,即ageChanged(int)
61, 55, 34, 34, 0x05, //第二个自定义信号的签名存储在qt_meta_stringdata_Object数组中,
//索引是61,即scoreChanged(int)
// slots: signature, parameters, type, tag, flags
79, 35, 34, 34, 0x0a, //第一个自定义槽函数的签名存储在qt_meta_stringdata_Object数组中,
//索引是79,即onAgeChanged(int)
97, 55, 34, 34, 0x0a, //第二个自定义槽函数的签名存储在qt_meta_stringdata_Object数组中,
//索引是79,即onScoreChanged(int)
// properties: name, type, flags
35, 117, 0x02495103, // 第一个自定义属性的签名存储在qt_meta_stringdata_Object中,索引是35,即age
55, 117, 0x02495103, // 第二个自定义属性的签名存储在qt_meta_stringdata_Object中,索引是55,即score
127, 121, 0x0009510b, // 第三个自定义属性的签名存储在qt_meta_stringdata_Object中,索引是127,即level
// properties: notify_signal_id //属性关联的信号编号
0,
1,
0,
// enums: name, flags, count, data
121, 0x0, 4, 54, //枚举的定义,存储在qt_meta_stringdata_Object中,索引是121,即Level,内含4个枚举常量
// enum data: key, value //枚举数据的键值对
133, uint(Object::Basic), //数组索引是133,即Basic
139, uint(Object::Middle), //数组索引是139,即Middle
146, uint(Object::Advanced), //数组索引是146,即Advanced
155, uint(Object::Master), //数组索引是155,即Master
0 // eod 元数据结束标记
};
内省表是一个 uint 数组,分为五个部分:第一部分content,即内容,分为9行。第一行revision,指MOC生成代码的版本号(Qt4 是6,Qt5则是7)。第二个classname,即类名,该值是一个索引,指向字符串表的某一个位置(本例中就是第0位)。
static const char qt_meta_stringdata_Object[] = {
"Object\0Scorpio\0Author\0""1.0\0Version\0\0"
"age\0ageChanged(int)\0score\0scoreChanged(int)\0"
"onAgeChanged(int)\0onScoreChanged(int)\0"
"int\0Level\0level\0Basic\0Middle\0Advanced\0"
"Master\0"
};
5、信号的实现
MOC在生成的moc_xxx.cpp文件中实现了信号,创建了一个指向参数的指针的数组,并将指针数组传给QMetaObject::activate函数。数组的第一个元素是返回值。本例中值是0,因为返回值是void。传给activate函数的第三个参数是信号的索引(本例中是0)。
// SIGNAL 0,ageChanged信号的实现
void Object::ageChanged(int _t1)
{
void *_a[] = { 0, const_cast(reinterpret_cast(&_t1)) };
QMetaObject::activate(this, &staticMetaObject, 0, _a);
}
// SIGNAL 1 scoreChanged信号的实现
void Object::scoreChanged(int _t1)
{
void *_a[] = { 0, const_cast(reinterpret_cast(&_t1)) };
QMetaObject::activate(this, &staticMetaObject, 1, _a);
}
6、槽函数的调用
利用槽函数在qt_static_metacall 函数的索引位置来调用槽函数:
void Object::qt_static_metacall(QObject *_o, QMetaObject::Call _c, int _id, void **_a)
{
if (_c == QMetaObject::InvokeMetaMethod) {
Q_ASSERT(staticMetaObject.cast(_o));
Object *_t = static_cast