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_OBJECTpublic: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.)"
#endifQT_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_LITERALstatic 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