() const { return m_value; }
public slots:
void setValue(int value);
signals:
void valueChanged(int newValue);
private:
int m_value;
};
注意:这个宏要求类是QObject的子类。使用Q_GADGET或Q_GADGET_EXPORT而不是Q_OBJECT来启用元对象系统对非QObject子类中的枚举的支持。
Q_OBJECT宏我们可以看到,主要是做了三件事:
1.将指定的类注册进入到元对象系统内,至于什么是元对象系统,我们接下来会说,你先知道是注册进元对象系统就行了
2.添加信号与槽函数的注册
3.注册Qt的属性系统
这三个功能其实也构成了Qt这套框架的全部,可以说Qt整套系统都是围绕着Q_OBJECT宏来做的。
1.元对象系统
元对象系统
Qt的元对象系统(Meta-Object System)为对象间通信、运行时类型信息和动态属性系统提供了信号和槽机制。元对象系统基于三个方面:
-
QObject类为可以利用元对象系统的对象提供了一个基类。
-
类声明的私有部分中的Q_OBJECT宏用于启用元对象功能,如动态属性、信号和插槽。
-
元对象编译器(moc)为每个QObject子类提供实现元对象特性所需的代码。
我们可以理解为,元对象系统就是Qt的一个“C#化”的尝试,即将原来在C++中不可见的一切
moc工具读取一个C++源文件。如果它找到一个或多个包含Q_OBJECT宏的类声明,它将生成另一个C++源文件,该文件包含每个类的元对象代码。这个生成的源文件要么被#包含到类的源文件中,要么更常见的是,被编译并链接到类的实现中。
除了提供用于对象之间通信的信号和槽机制(引入该系统的主要原因)之外,元对象代码还提供以下附加功能:
-
QObject::metaObject()返回类的关联元对象。
-
QMetaObject::className()在运行时以字符串形式返回类名,而不需要通过C++编译器支持本机运行时类型信息(RTTI)。
-
函数返回对象是否是继承QObject继承树中指定类的类的实例。
-
QObject::tr()转换字符串以进行国际化。
-
QObject::setProperty()和QOobject::property()按名称动态设置和获取属性。
-
QMetaObject::newInstance()构造类的一个新实例。
还可以使用qobject_cast()对qobject类执行动态强制转换。qobject_cast()函数的行为类似于标准C++dynamic_cast(),其优点是不需要RTTI支持,并且可以跨动态库边界工作。它尝试将其参数强制转换为尖括号中指定的指针类型,如果对象的类型正确(在运行时确定),则返回非零指针,如果对象类型不兼容,则返回nullptr。
虽然可以在没有Q_OBJECT宏和元对象代码的情况下使用QObject作为基类,但如果不使用Q_OBJECT宏,则信号和插槽以及此处描述的其他功能都不可用。
从元对象系统的角度来看,一个没有元代码的QObject子类等价于它最接近的有元对象代码的祖先。
这意味着,例如,QMetaObject::className()不会返回类的实际名称,而是返回该祖先的类名。
因此,我们强烈建议QObject的所有子类使用Q_OBJECT宏,无论它们是否实际使用信号、槽和属性。
2.信号与槽
在Qt中的信号与槽可以说是Qt的头牌系统,也是Qt这套东西能够如此流行的重要原因,也是整个Qt框架最重要的基石。
当然了,其实自己实现一套Qt的Signal - Slot的系统其实并不复杂,而且肯定很多人已经能开发一套类似的东西了。比如我简单打个样:
class Caller {
public:
using CallMethod = void(*)(const QString& sModule, const QString& sDescribe, const QString& sVariable, const QVariant& extra);
using SendCMD = void(*)(const QString& sModule, const QString& sDescribe, const QString& sVariable, const QVariant& extra);
void RegisterCallMethod(CallMethod callback) {
callbacks_.append(callback);
}
void RegisterSendCMD(SendCMD callback) {
sendcmds_.append(callback);
}
void Signal_CallMethod(const QString& sModule, const QString& sDescribe, const QString& sVariable, const QVariant& extra) {
for (CallMethod callback : callbacks_) {
if (callback) {
callback(sModule, sDescribe, sVariable, extra);
}
}
}
void Signal_SendCMD(const QString& sModule, const QString& sDescribe, const QString& sVariable, const QVariant& extra) {
for (SendCMD callback : callbacks_) {
if (callback) {
callback(sModule, sDescribe, sVariable, extra);
}
}
}
private:
QList<CallMethod> callbacks_;
QList<SendCMD> sendcmds_;
};
但是Qt的signal - slot 强大的地方就在于它的封装性和灵活性,各种注销注册操作相对自己写回调函数还是简单很多很多的。你想啊,原先需要这么多代码的地方,现在只需要一个宏,或者一句话,难易程度几乎无法比较。
由于Qt独特的signal索引机制,导致其网络相关的库效率可能是C++回调函数的百分之一,这是非常夸张的性能损失,但是这在某些性能不关键的场景仍然是可以接受的。
Signals & Slots
Signals 和Slots用于对象之间的通信。Signals 和Slots机制是Qt的一个核心功能,可能也是与其他框架提供的功能最不同的部分。Qt的元对象系统使Signals 和Slots成为可能。
其他工具包使用回调来实现这种通信。回调是指向函数的指针,因此,如果您希望处理函数通知您某个事件,您可以将指向另一个函数的指针(回调)传递给处理函数。然后,处理函数在适当的时候调用回调。虽然使用这种方法的成功框架确实存在,但回调可能是不直观的,并且在确保回调