提到Qt,大家首先想到的可能就是信号和槽通信,但是他们是如何实现的呢?
本系列内容就和大家一起探究Qt信号槽通信机制的原理。
Qt环境:Qt5.5.1
高版本Qt又增加了很多宏,不方便初学者理解。
一、 官方信号槽例子
1. 头文件
#include <QObject>
class Counter : public QObject
{
Q_OBJECT
public:
explicit Counter() {m_value = 0;}
public:
int value() const { return m_value; }
signals:
void signal_valueChanged(int newValue);
public slots:
void slot_setValue(int value);
private:
int m_value;
};
2. 源文件
void Counter::slot_setValue(int value)
{
if (value != m_value) {
m_value = value;
emit signal_valueChanged(value);
}
}
3. main函数中调用及结果
Counter a, b;
QObject::connect(&a, SIGNAL(signal_valueChanged(int)),
&b, SLOT(slot_setValue(int)));
a.slot_setValue(12); //a.value() = 12, b.value() = 12
b.slot_setValue(48); //a.value() = 12, b.value() = 48
这是最初的语法,从 1992 年 Qt 发明以来就没有变化。
但是,即使从一开始最基本的 API 没有改变,其底层实现被修改过好多次。在底层添加了许多新的特性,也有很多内容发生了变化。不过,即便如此,也并没有引入任何魔法,接下来我们将慢慢剖析。
二、 MOC——元对象编译器
我们知道信号槽使用的前提是继承QObject或其子类并且声明了Q_OBJECT宏。
但是这个宏是谁解析的呢?
Qt信号槽和属性系统【不做扩展】是基于运行时的自省能力。Qt开发时C++并没有Qt所需要的内省能力,所以Qt便自己扩展了C++,提供了这种能力——MOC,元对象编译器。
它处理头文件,重新生成C++文件,将这些文件连同剩余的C++文件再一起编译。这些新生成的文件包含了内省所需要的信息。
三.那些熟悉的宏
下面的这些关键字显示都不是标准C++所能识别的,但是我们在编译时为什么没有报错呢?因为它们都是一些宏,在qobjectdefs.h中定义。
signals 、slots 、emit 、SIGNAL 、SLOT
# define slots
# define signals public
槽函数就是普通的函数,编译器对他们就像对待其他函数一样。这些宏的另外一个目的是它们可以被MOC识别。
信号函数的代码由MOC生成,并且没有返回值。
# define emit
emit是一个空宏,而且MOC也不会处理它。所以当我们发出信号时可以无需加emit,但是为了提高代码的可读性,通常都是加上emit关键字的。
Q_CORE_EXPORT const char *qFlagLocation(const char *method);
#ifndef QT_NO_META_MACROS
#ifndef QT_NO_DEBUG
# define QLOCATION "\0" __FILE__ ":" QT_STRINGIFY(__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
这些宏仅由预处理器使用,将参数转换成字符串,并且在之前添加一个代码。
在调试模式下,我们还会将这些字符串追加上所在文件的位置作为信号无法正常连接的警告信息。关于一些宏的用法大家可自行查询,或者直接编码查看效果。
#define Q_OBJECT \
public: \
Q_OBJECT_CHECK \
QT_WARNING_PUSH \
Q_OBJECT_NO_OVERRIDE_WARNING \
static const QMetaObject staticMetaObject; \
virtual const QMetaObject *metaObject() const; \
virtual void *qt_metacast(const char *); \
virtual int qt_metacall(QMetaObject::Call, int, void **); \
QT_WARNING_POP \
QT_TR_FUNCTIONS \
private: \
Q_DECL_HIDDEN_STATIC_METACALL static void qt_static_metacall(QObject *, QMetaObject::Call, int, void **); \
struct QPrivateSignal {};
Q_OBJECT宏定义了一个静态的staticMetaObject对象和一系列其他的函数,这些将由MOC生成的moc_**.cpp中实现。
四、小结
看到这块大家可能会比较疑惑,这些是和信号槽有一些关系,但是根本无法看出信号和槽的原理啊!的确是这样的,无法看出来。
欲知后事如何,关注公众号后且听下回分解。