Qt的元对象系统(Meta-Object System)是Qt框架的核心功能之一,为C++语言增加了一些动态特性,借助元对象系统Qt可以实现以下功能
- 信号与槽机制(Signals and Slots)
- 运行时类型信息(Run-Time Type Information, RTTI)
- 属性系统(Property System)
- 动态对象创建(Dynamic Object Creation)
- 动态方法调用(Dynamic Method Invocation)
- 安全的类型转换(Dynamic Cast)
元对象系统的实现需要借助moc (Meta-Object_Compiler)工具, moc工具扫描包含Q_OBJECT宏的头文件,为为我们自动生成了元对象系统的实现代码,我们以一个简单的MainWindow例子来探究一下moc系统有什么黑魔法,示例完整代码放在文章最后。
QObject派生类的元信息编码和存储
元(Meta)在C++中是和编译期相关的概念,元对象用来存储一个类在编译器就确定的类型相关信息, 即每一个类的类型和方法信息都是编译期都是已经确定了的,在一般自己实现C++反射系统的方案中,存储类的元信息,无不例外的都是借助一个静态成员变量,由于静态变量是存储在静态存储区,生命周期伴随整个程序,并且其初始化是在程序加载后,main入口函数之前,所以用一个静态类来存储元信息便很自然了,如下,Qt中就是给每一个定义Q_OBJECT宏的类定义了一个static QMetaObject成员对象。
staticMetaObject这个静态成员的定义就在moc工具自动生成的moc_mainwindow.cpp文件之中,其中主要包含类的派生信息和信号与槽函数信息。
qt_meta_stringdata_MainWindow和qt_meta_data_MainWindow的定义在下面两张截图之中,我们先来看一下qt_meta_stringdata_MainWindow,这个结构里面将信号和槽函数的信息编码存储到了一个字符串之中,QT_MOC_LITERAL(0, 0, 10)中,第一个参数为编号,第二个为在下面字符串中的起始位置,第二个参数为函数名称的长度。
qt_meta_data_MainWindow这个结构指定了每个信号和槽函数在上面的“字符串”中的位置以及函数的返回值,参数类型信息,这两个结构就编码存储了信号槽函数的所有信息了。
槽函数的调用
这里先讨论一个问题,如果知道一个函数的字符串名称,怎么调用这个函数? 注意,这里的问题是,你现在只知道一个字符串, 虽然你知道它是那个函数,但是你怎么来调用?
std::string func_name = "my_slot";
//how to call my_slot() ?
func_name()? // this is error~
//the possiable way
if(func_name == "my_slot"){
my_slot();
} else if( func_name = "my_slot1"){
my_slot1();
}
在老版本的Qt中,connect链接信号和槽的时候,也是直接连接的函数字符串名称,通过字符串来调用函数,就不得不借用我们moc文件里面生成的qt_static_metacall了,这里的实现也是很明了,首先找到函数的索引,然后通过switch case进行函数调用。
void MainWindow::qt_static_metacall(QObject *_o, QMetaObject::Call _c, int _id, void **_a)
{
if (_c == QMetaObject::InvokeMetaMethod) {
auto *_t = static_cast<MainWindow *>(_o);
Q_UNUSED(_t)
switch (_id) {
case 0: _t->my_signal(); break;
case 1: _t->my_signal_param((*reinterpret_cast< int(*)>(_a[1])),(*reinterpret_cast< double(*)>(_a[2]))); break;
case 2: _t->on_actionExit_triggered(); break;
case 3: _t->my_slot(); break;
case 4: _t->my_slot_param((*reinterpret_cast< int(*)>(_a[1]))); break;
default: ;
}
} else if (_c == QMetaObject::IndexOfMethod) {
int *result = reinterpret_cast<int *>(_a[0]);
{
using _t = void (MainWindow::*)();
if (*reinterpret_cast<_t *>(_a[1]) == static_cast<_t>(&MainWindow::my_signal)) {
*result = 0;
return;
}
}
{
using _t = void (MainWindow::*)(int , double );
if (*reinterpret_cast<_t *>(_a[1]) == static_cast<_t>(&MainWindow::my_signal_param)) {
*result = 1;
return;
}
}
}
}
信号函数的实现
我们在定义信号的时候,只给出了一个信号函数的申明,信号函数的实现也是moc自动生成的
// SIGNAL 0
void MainWindow::my_signal()
{
QMetaObject::activate(this, &staticMetaObject, 0, nullptr);
}
// SIGNAL 1
void MainWindow::my_signal_param(int _t1, double _t2)
{
void *_a[] = { nullptr, const_cast<void*>(reinterpret_cast<const void*>(std::addressof(_t1))), const_cast<void*>(reinterpret_cast<const void*>(std::addressof(_t2))) };
QMetaObject::activate(this, &staticMetaObject, 1, _a);
}
总结:
我们可以发现,QT的元对象系统并没有什么神奇之处,主要干了下面几件事情
- 通过一个静态成员QMetaObject存储类的元信息
- 通过一个字符串编码存储了信号和槽函数信息
- 通过信号和槽函数的索引,在switch case中调用槽函数
- 自动生成信号函数的实现代码
mainwindow示例:
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
namespace Ui {
class MainWindow;
}
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = 0);
explicit MainWindow(QWidget* parent, int id);
~MainWindow();
void my_general_fun();
private slots:
void on_actionExit_triggered();
void my_slot();
void my_slot_param(int iiii);
signals:
void my_signal();
void my_signal_param(int jjjj, double kkkk);
private:
Ui::MainWindow *ui;
};
#endif // MAINWINDOW_H
moc生成的实现代码:
/****************************************************************************
** Meta object code from reading C++ file 'mainwindow.h'
**
** Created by: The Qt Meta Object Compiler version 67 (Qt 5.15.2)
**
** WARNING! All changes made in this file will be lost!
*****************************************************************************/
#include <memory>
#include "../../../../mainwindow.h"
#include <QtCore/qbytearray.h>
#include <QtCore/qmetatype.h>
#if !defined(Q_MOC_OUTPUT_REVISION)
#error "The header file 'mainwindow.h' doesn't include <QObject>."
#elif Q_MOC_OUTPUT_REVISION != 67
#error "This file was generated using the moc from 5.15.2. It"
#error "cannot be used with the include files from this version of Qt."
#error "(The moc has changed too much.)"
#endif
QT_BEGIN_MOC_NAMESPACE
QT_WARNING_PUSH
QT_WARNING_DISABLE_DEPRECATED
struct qt_meta_stringdata_MainWindow_t {
QByteArrayData data[10];
char stringdata0[99];
};
#define QT_MOC_LITERAL(idx, ofs, len) \
Q_STATIC_BYTE_ARRAY_DATA_HEADER_INITIALIZER_WITH_OFFSET(len, \
qptrdiff(offsetof(qt_meta_stringdata_MainWindow_t, stringdata0) + ofs \
- idx * sizeof(QByteArrayData)) \
)
static const qt_meta_stringdata_MainWindow_t qt_meta_stringdata_MainWindow = {
{
QT_MOC_LITERAL(0, 0, 10), // "MainWindow"
QT_MOC_LITERAL(1, 11, 9), // "my_signal"
QT_MOC_LITERAL(2, 21, 0), // ""
QT_MOC_LITERAL(3, 22, 15), // "my_signal_param"
QT_MOC_LITERAL(4, 38, 4), // "jjjj"
QT_MOC_LITERAL(5, 43, 4), // "kkkk"
QT_MOC_LITERAL(6, 48, 23), // "on_actionExit_triggered"
QT_MOC_LITERAL(7, 72, 7), // "my_slot"
QT_MOC_LITERAL(8, 80, 13), // "my_slot_param"
QT_MOC_LITERAL(9, 94, 4) // "iiii"
},
"MainWindow\0my_signal\0\0my_signal_param\0"
"jjjj\0kkkk\0on_actionExit_triggered\0"
"my_slot\0my_slot_param\0iiii"
};
#undef QT_MOC_LITERAL
static const uint qt_meta_data_MainWindow[] = {
// content:
8, // revision
0, // classname
0, 0, // classinfo
5, 14, // methods
0, 0, // properties
0, 0, // enums/sets
0, 0, // constructors
0, // flags
2, // signalCount
// signals: name, argc, parameters, tag, flags
1, 0, 39, 2, 0x06 /* Public */,
3, 2, 40, 2, 0x06 /* Public */,
// slots: name, argc, parameters, tag, flags
6, 0, 45, 2, 0x08 /* Private */,
7, 0, 46, 2, 0x08 /* Private */,
8, 1, 47, 2, 0x08 /* Private */,
// signals: parameters
QMetaType::Void,
QMetaType::Void, QMetaType::Int, QMetaType::Double, 4, 5,
// slots: parameters
QMetaType::Void,
QMetaType::Void,
QMetaType::Void, QMetaType::Int, 9,
0 // eod
};
void MainWindow::qt_static_metacall(QObject *_o, QMetaObject::Call _c, int _id, void **_a)
{
if (_c == QMetaObject::InvokeMetaMethod) {
auto *_t = static_cast<MainWindow *>(_o);
Q_UNUSED(_t)
switch (_id) {
case 0: _t->my_signal(); break;
case 1: _t->my_signal_param((*reinterpret_cast< int(*)>(_a[1])),(*reinterpret_cast< double(*)>(_a[2]))); break;
case 2: _t->on_actionExit_triggered(); break;
case 3: _t->my_slot(); break;
case 4: _t->my_slot_param((*reinterpret_cast< int(*)>(_a[1]))); break;
default: ;
}
} else if (_c == QMetaObject::IndexOfMethod) {
int *result = reinterpret_cast<int *>(_a[0]);
{
using _t = void (MainWindow::*)();
if (*reinterpret_cast<_t *>(_a[1]) == static_cast<_t>(&MainWindow::my_signal)) {
*result = 0;
return;
}
}
{
using _t = void (MainWindow::*)(int , double );
if (*reinterpret_cast<_t *>(_a[1]) == static_cast<_t>(&MainWindow::my_signal_param)) {
*result = 1;
return;
}
}
}
}
QT_INIT_METAOBJECT const QMetaObject MainWindow::staticMetaObject = { {
QMetaObject::SuperData::link<QMainWindow::staticMetaObject>(),
qt_meta_stringdata_MainWindow.data,
qt_meta_data_MainWindow,
qt_static_metacall,
nullptr,
nullptr
} };
const QMetaObject *MainWindow::metaObject() const
{
return QObject::d_ptr->metaObject ? QObject::d_ptr->dynamicMetaObject() : &staticMetaObject;
}
void *MainWindow::qt_metacast(const char *_clname)
{
if (!_clname) return nullptr;
if (!strcmp(_clname, qt_meta_stringdata_MainWindow.stringdata0))
return static_cast<void*>(this);
return QMainWindow::qt_metacast(_clname);
}
int MainWindow::qt_metacall(QMetaObject::Call _c, int _id, void **_a)
{
_id = QMainWindow::qt_metacall(_c, _id, _a);
if (_id < 0)
return _id;
if (_c == QMetaObject::InvokeMetaMethod) {
if (_id < 5)
qt_static_metacall(this, _c, _id, _a);
_id -= 5;
} else if (_c == QMetaObject::RegisterMethodArgumentMetaType) {
if (_id < 5)
*reinterpret_cast<int*>(_a[0]) = -1;
_id -= 5;
}
return _id;
}
// SIGNAL 0
void MainWindow::my_signal()
{
QMetaObject::activate(this, &staticMetaObject, 0, nullptr);
}
// SIGNAL 1
void MainWindow::my_signal_param(int _t1, double _t2)
{
void *_a[] = { nullptr, const_cast<void*>(reinterpret_cast<const void*>(std::addressof(_t1))), const_cast<void*>(reinterpret_cast<const void*>(std::addressof(_t2))) };
QMetaObject::activate(this, &staticMetaObject, 1, _a);
}
QT_WARNING_POP
QT_END_MOC_NAMESPACE