QT 中的元對象系統(一):元對象和元數據

目錄

1.為什么需要元系統

2.元數據

3.模擬元對象系統

3.1.元對象聲明

3.2.對C++擴展

3.3初始化元對象

3.4.使用元對象

4.QT的元系統

4.1.元對象系統基于QObject類、Q_OBJECT宏、元對象編譯器MOC實現

4.2.元對象系統的功能

4.3.Q_PROPERTY()的使用

4.4.Q_INVOKABLE使用


1.為什么需要元系統

????????Qt 作為跨平臺的GUI框架,在實際項目中應用廣泛,在日常的使用中,隨手使用的一些機制(如著名的信號槽機制),屬性(如Property系統),以及重載各種事件函數來完成定制化;還有qml中直接訪問QObject的Property。

? ? ? ? 在Qt項目中,可以直接通過類名創建對象:

class MyClass : public QObject {Q_OBJECTQ_PROPERTY(int age READ age  WRITE setAge NOTIFY ageChanged)
public:MyClass(QObject *parent = nullptr) : QObject(parent) {}void myMethod() { qDebug() << "Hello, world!"; }
};//根據類名創建對象
const QMetaObject *metaObject = MyClass::staticMetaObject();
QObject *myObject = metaObject->newInstance(Q_ARG(QObject*, nullptr));

? ? ? ? 可以函數名直接訪問類的方法:

QMetaObject::invokeMethod(myObject, "myMethod");

????????運行時增加屬性,在運行時根據當前的上下文為一個對象增加或者刪除屬性,并且要做到在其他地方使用的時候無感——就像這個屬性原來就聲明在類中一樣:

MyClass obj;
obj.setProperty("age", 10); //固定屬性,事先聲明定義好的
obj.setProperty("name", 110); //動態屬性

等等,有了元系統,使得在掌握很少類內部信息都能完成類數據的獲取和方法的調用。

2.元數據

元數據是描述數據的數據,它提供了關于數據對象的附加信息。在Qt中,元數據通常用于描述類的屬性、方法、信號和槽等信息。試想一下,我們會怎么描述一個類 MyClass:

class MyClass : public Object
{
public:MyClass();~MyClass();enum Type{//... };
public:virtual void fool() override;void bar();//...
};
  • 這個類的類名為MyClass
  • 繼承了一個基類 Object
  • 有一個無參的構造函數和一個析構函數
  • 實現了繼承來的一個虛方法
  • 自己有一個名為bar的public方法
  • 內定義了一個枚舉類型
  • ...

上述描述內容就是元數據,用來描述我們聲明的一個class,如果我們把以上數據封裝為一個類,我們簡單的認為這個類就是元對象。

3.模擬元對象系統

Qt 的元對象系統發展這么久,完善是真的完善,代碼多也是真的多!在迷失于復雜繁瑣的源代碼中之前,不妨先來設計一個簡單的元對象系統來幫助我們理解思想。

3.1.元對象聲明

聯系前面的元數據的說明,樸素的想法是我們可以用另一個對象來描述這些信息,即元對象,在運行時通過這個對象來獲取相關的具體類型等。

根據我們的需要,元對象應該具有以下信息

  • 類型名
  • 繼承的父類信息
  • 成員函數的信息
  • 內部定義的枚舉變量可能也是需要的
  • ...

看起來像是這樣

class MetaObject
{
public:// 其他成員函數// ...private:// 簡單起見,直接用對象了ClassInfo m_info;ClassInfo* m_superClass;ClassMethod m_methods;ClassEnums m_enums;
};

3.2.對C++擴展

為了使我們能在軟件系統中有效的管理,我們需要對MyClass做一些拓展,現在MyClass看上去像這樣:

// MyClass.h
class MyClass : public Object
{// ... 和之前一樣// 重寫一個來自Object的虛方法virtual const MetaObject *metaObject() const override;static const MetaObject staticMetaObject;   // 一個靜態成員
};

現在,只要這個數據能夠正確初始化,如果我們需要,我們就可以借助多態的特性,通過接口來獲得這個類的相關信息了。

3.3初始化元對象

那么問題來了,怎么初始化這個變量呢,C++ 作為靜態語言,想要獲取這些編譯期有關的信息,我們只能選擇在編譯時或者編譯前來做這件事,直覺告訴我們,我們要做編譯器之前來做這件事,有兩個顯而易見的原因

  1. 不要妄圖修改編譯器,成本巨大且危險
  2. 直接修改編譯器顯示不是用戶能接受的方式

當然可以手動編寫這個文件,把類的信息一個個提煉出來,但是那樣太不程序員了,我們需要寫一段程序,在編譯器之前來做這個事情(你可以把它當成一段生成代碼的腳本),我們可以這樣做:

  1. 在我們寫的類里面加上一個標記,來表示該類使用了元對象,需要處理并正確初始化 MetaObejct,我們這里假設就用 DEBUG_OBJ 來表示
  2. 運行我們的程序,如果在某個文件里面發現了標記,解析這個文件,獲取他的類型信息(ClassInfo),方法信息(ClassMethod),繼承信息等
  3. 腳本生成了一個 moc_MyClass.cpp 文件,用上述信息初始化 MetaObject,類似于下面這樣:
// 由腳本生成的文件
// moc_MyClass.cpp
#include "MyClass.h"// 這里是腳本解析原來頭文件生成的數據
// 解析了類的名稱,成員,繼承關系等等
// ...const MetaObject MyClass::staticMetaObject = {// 用解析來的數據來初始化元對象內容
};const MetaObject *MyClass::metaObject() const
{return &staticMetaObject;
}

然后把這個文件也為做源文件一起編譯就行了。

3.4.使用元對象

現在再回頭來看前面的問題

1)現在直接通過虛函數多態性質拿到 MetaObject,再拿到元數據,比較兩個類名是不是一致即可,如果我們采用靜態的字符串數組來存類名,甚至我們不需要比較字符串是否一致,只需要比較字符串指針是否相同就可以了。

2)現在直接綁定兩個對象的方法字符串即可,我們可以在 MetaObject 提供兩各方法

  • 檢查這兩個字符串是否是類的方法(ClassMethod中有沒有這個字符串以及參數檢查),以判斷綁定是否能成功
  • 一個統一的調用形式,內部根據字符串來調用相關方法

3)現在你可添加屬性,實際添加到元數據中,而存取就像你調用get,set方法一樣自然

大功告成,至此,一個簡單的元對象系統就設計好了!

4.QT的元系統

    4.1.元對象系統基于QObject類、Q_OBJECT宏、元對象編譯器MOC實現

    1) QObject 類
    作為每一個需要利用元對象系統的類的基類。
    2) Q_OBJECT宏
    定義在每一個類的私有數據段,用來啟用元對象功能,比如動態屬性、信號和槽。
    在一個QObject類或者其派生類中,如果沒有聲明Q_OBJECT宏,那么類的metaobject對象不會被生成,類實例調用metaObject()返回的就是其父類的metaobject對象,導致的后果是從類的實例獲得的元數據其實都是父類的數據。因此類所定義和聲明的信號和槽都不能使用,所以,任何從QObject繼承出來的類,無論是否定義聲明了信號、槽和屬性,都應該聲明Q_OBJECT 宏。
    3) 元對象編譯器MOC (Meta Object Complier)
    MOC分析C++源文件,如果發現在一個頭文件(header file)中包含Q_OBJECT 宏定義,會動態的生成一個moc_xxxx命名的C++源文件,源文件包含Q_OBJECT的實現代碼,會被編譯、鏈接到類的二進制代碼中,作為類的完整的一部分。

    4.2.元對象系統的功能

    qt元對象系統主要提供了三個能力:

    • 對象間通信(信號槽機制)
    • 運行時信息(類似反射機制)
    • 動態的屬性系統

    除了這些功能外,還提供了如下功能:

    • QObject::metaObject()?返回與該類相關聯的元對象。
    • QMetaObject::className()?在運行時以字符串形式返回類名,而無需通過 C++ 編譯器提供本地運行時類型信息(RTTI)支持。
    • QObject::inherits()?函數返回一個對象是否是在 QObject 繼承樹內繼承了指定類的實例。
    • QObject::tr()?和?QObject::trUtf8()?用于國際化的字符串翻譯。
    • QObject::setProperty()?和?QObject::property()?動態地通過名稱設置和獲取屬性。
    • QMetaObject::newInstance()?構造該類的新實例。
    • ?使用qobject_cast()方法在QObject類之間提供動態轉換,qobject_cast()方法的功能類似于標準C++的dynamic_cast(),但qobject_cast()不需要RTTI的支持

    4.3.Q_PROPERTY()的使用

    #define Q_PROPERTY(text)

    Q_PROPERTY定義在/src/corelib/kernel/Qobjectdefs.h文件中,用于被MOC處理。

    Q_PROPERTY(type nameREAD getFunction[WRITE setFunction][RESET resetFunction][NOTIFY notifySignal][REVISION int][DESIGNABLE bool][SCRIPTABLE bool][STORED bool][USER bool][CONSTANT][FINAL])

    Type:屬性的類型
    Name:屬性的名稱
    READ getFunction:屬性的訪問函數
    WRITE setFunction:屬性的設置函數
    RESET resetFunction:屬性的復位函數
    NOTIFY notifySignal:屬性發生變化的地方發射的notifySignal信號
    REVISION int:屬性的版本,屬性暴露到QML中
    DESIGNABLE bool:屬性在GUI設計器中是否可見,默認為true
    SCRIPTABLE bool:屬性是否可以被腳本引擎訪問,默認為true
    STORED bool:
    USER bool:
    CONSTANT:標識屬性的值是常量,值為常量的屬性沒有WRITE、NOTIFY
    FINAL:標識屬性不會被派生類覆寫
    注意:NOTIFY notifySignal聲明了屬性發生變化時發射notifySignal信號,但并沒有實現,因此程序員需要在屬性發生變化的地方發射notifySignal信號。
    Object.h:

    #ifndef OBJECT_H
    #define OBJECT_H#include <QObject>
    #include <QString>
    #include <QDebug>class Object : public QObject
    {Q_OBJECTQ_PROPERTY(int age READ age  WRITE setAge NOTIFY ageChanged)Q_PROPERTY(int score READ score  WRITE setScore NOTIFY scoreChanged)Q_CLASSINFO("Author", "Scorpio")Q_CLASSINFO("Version", "1.0")Q_ENUMS(Level)
    protected:QString m_name;QString m_level;int m_age;int m_score;
    public:enum Level{Basic,Middle,Advanced};
    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;emit scoreChanged(m_score);}
    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.cpp:

    #include <QCoreApplication>
    #include "Object.h"int main(int argc, char *argv[])
    {QCoreApplication a(argc, argv);Object ob("object");//設置屬性ageob.setProperty("age", QVariant(30));qDebug() << "age: " << ob.age();qDebug() << "property age: " << ob.property("age").toInt();//設置屬性scoreob.setProperty("score", QVariant(90));qDebug() << "score: " << ob.score();qDebug() << "property score: " << ob.property("score").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();
    }

    4.4.Q_INVOKABLE使用

    #define Q_INVOKABLE

    ????????Q_INVOKABLE定義在/src/corelib/kernel/Qobjectdefs.h文件中,用于被MOC識別。
    ????????Q_INVOKABLE宏用于定義一個成員函數可以被元對象系統調用,Q_INVOKABLE宏必須寫在函數的返回類型之前。如下:

    Q_INVOKABLE void invokableMethod();

    ????????invokableMethod()函數使用了Q_INVOKABLE宏聲明,invokableMethod()函數會被注冊到元對象系統中,可以使用 QMetaObject::invokeMethod()調用。
    ????????Q_INVOKABLE與QMetaObject::invokeMethod均由元對象系統喚起,在Qt C++/QML混合編程、跨線程編程、Qt Service Framework以及?Qt/ HTML5混合編程以及里廣泛使用。
    1) 在跨線程編程中的使用
    如何調用駐足在其他線程里的QObject方法呢?Qt提供了一種非常友好而且干凈的解決方案:向事件隊列post一個事件,事件的處理將以調用所感興趣的方法為主(需要線程有一個正在運行的事件循環)。而觸發機制的實現是由MOC提供的內省方法實現的。因此,只有信號、槽以及被標記成Q_INVOKABLE的方法才能夠被其它線程所觸發調用。如果不想通過跨線程的信號、槽這一方法來實現調用駐足在其他線程里的QObject方法。另一選擇就是將方法聲明為Q_INVOKABLE,并且在另一線程中用invokeMethod喚起。
    2) Qt Service Framework
    Qt服務框架是Qt Mobility 1.0.2版本推出的,一個服務(service)是一個獨立的組件提供給客戶端(client)定義好的操作。客戶端可以通過服務的名稱,版本號和服務的對象提供的接口來查×××。 查找到服務后,框架啟動服務并返回一個指針。
    服務通過插件(plug-ins)來實現。為了避免客戶端依賴某個具體的庫,服務必須繼承自QObject,保證QMetaObject?系統可以用來提供動態發現和喚醒服務的能力。要使QmetaObject機制充分的工作,服務必須滿足,其所有的方法都是通過 signal、slot、property或invokable method和Q_INVOKEBLE來實現。

    QServiceManager manager;
    QObject *storage ;  
    storage = manager.loadInterface("com.nokia.qt.examples.FileStorage"); 
    if(storage)     QMetaObject::invokeMethod(storage, "deleteFile", Q_ARG(QString, "/tmp/readme.txt")); 

    上述代碼通過service的元對象提供的invokeMethod方法,調用文件存儲對象的deleteFile() 方法。客戶端不需要知道對象的類型,因此也沒有鏈接到具體的service庫。?當然在服務端的deleteFile方法,一定要被標記為Q_INVOKEBLE,才能夠被元對象系統識別。
    Qt服務框架的一個亮點是它支持跨進程通信,服務可以接受遠程進程。在服務管理器上注冊后,進程通過signal、slot、invokable method和property來通信,就像本地對象一樣。服務可以設定為在客戶端間共享,或針對一個客戶端。?在Qt服務框架推出之前,信號、槽以及invokable method僅支持跨線程。 下圖是跨進程的服務/客戶段通信示意圖。invokable method和Q_INVOKEBLE?是跨進城、跨線程對象之間通信的重要利器。

    本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
    如若轉載,請注明出處:http://www.pswp.cn/web/70658.shtml
    繁體地址,請注明出處:http://hk.pswp.cn/web/70658.shtml
    英文地址,請注明出處:http://en.pswp.cn/web/70658.shtml

    如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

    相關文章

    Pytorch實現之渾濁水下圖像增強

    簡介 簡介:這也是一篇非常適合GAN小白們上手的架構文章!提出了一種基于GAN的水下圖像增強網絡。這種網絡與其他架構類似,生成器是卷積+激活函數+歸一化+殘差結構的組成,鑒別器是卷積+激活函數+歸一化以及全連接層。損失函數是常用的均方誤差、感知損失和對抗損失三部分。 …

    TCPDF 任意文件讀取漏洞:隱藏在 PDF 生成背后的危險

    在網絡安全的世界里&#xff0c;漏洞就像隱藏在黑暗中的“定時炸彈”&#xff0c;稍有不慎就會引發災難性的后果。今天&#xff0c;我們要聊的是一個與 PDF 生成相關的漏洞——TCPDF 任意文件讀取漏洞。這個漏洞可能讓攻擊者輕松讀取服務器上的敏感文件&#xff0c;甚至獲取整個…

    【Git】六、企業級開發模型

    文章目錄 Ⅰ. 前言Ⅱ. 系統開發環境Ⅲ. Git 分支設計規范master分支release分支develop分支feature分支hotfix分支 Ⅰ. 前言 ? 我們知道&#xff0c;一個軟件從零開始到最終交付&#xff0c;大概包括以下幾個階段&#xff1a;規劃、編碼、構建、測試、發布、部署和維護。 ?…

    Kafka可視化工具EFAK(Kafka-eagle)安裝部署

    Kafka Eagle是什么&#xff1f; Kafka Eagle是一款用于監控和管理Apache Kafka的開源系統&#xff0c;它提供了完善的管理頁面&#xff0c;例如Broker詳情、性能指標趨勢、Topic集合、消費者信息等。 源代碼地址&#xff1a;https://github.com/smartloli/kafka-eagle 前置條件…

    C++:dfs,bfs各兩則

    1.木棒 167. 木棒 - AcWing題庫 喬治拿來一組等長的木棒&#xff0c;將它們隨機地砍斷&#xff0c;使得每一節木棍的長度都不超過 5050 個長度單位。 然后他又想把這些木棍恢復到為裁截前的狀態&#xff0c;但忘記了初始時有多少木棒以及木棒的初始長度。 請你設計一個程序…

    電子商務網站租用香港服務器的好處有哪些?

    電子商務網站租用香港服務器的好處主要包括&#xff1a; 香港服務器提供高速的網絡連接&#xff0c;國內訪問速度優勢明顯&#xff0c;滿足企業內部數據傳輸和遠程辦公需求。擁有國際出口帶寬優勢&#xff0c;實現與全球各地的高速連接&#xff0c;對跨國業務和海外市場拓展至關…

    stm32108鍵C-B全調性_動態可視化樂譜鋼琴

    108鍵全調性鋼琴 一 基本介紹1 項目簡介2 實現方式3 項目構成 二 實現過程0 前置基本外設驅動1 聲音控制2 樂譜錄入&基礎樂理3 點陣屏譜點動態刷新4 項目交互控制5 錄入新曲子過程 三 展示&#xff0c;與鏈接視頻地址1 主要功能函數一覽2 下載鏈接3 視頻效果 一 基本介紹 …

    【p-camera-h5】 一款開箱即用的H5相機插件,支持拍照、錄像、動態水印與樣式高度定制化。

    【開源推薦】p-camera-h5&#xff1a;一款輕量級H5相機插件開發實踐 一、插件背景 在Web開發中&#xff0c;原生攝像頭功能的集成往往面臨以下痛點&#xff1a; 瀏覽器兼容性問題視頻流與水印疊加實現復雜移動端適配困難功能定制成本高 為此&#xff0c;p-camera-h5 —— 一…

    交叉編譯curl(OpenSSL)移植ARM詳細步驟

    運行配置腳本 使用 Configure 腳本配置 OpenSSL&#xff0c;指定目標平臺和安裝路徑&#xff1a; curl downloads 各個版本 Old 1.1.1 Releases | OpenSSL Library 各個版本 從 OpenSSL 官網下載源碼包 tar -xzf openssl-1.1.1b.tar.gz cd openssl-1.1.1b/運行配置腳本 使…

    大語言模型中的 Token如何理解?

    在大語言模型中&#xff0c;Token 是文本處理的基本單元&#xff0c;類似于“文字塊”&#xff0c;模型通過將文本分割成Token來理解和生成內容。舉一個形象一點的例子&#xff0c;可以理解為 AI 處理文字時的“最小積木塊”。就像搭樂高時&#xff0c;每塊積木是基礎單位一樣&…

    表單制作代碼,登錄動畫背景前端模板

    炫酷動效登錄頁 引言 在網頁設計中,按鈕是用戶交互的重要元素之一。一個炫酷的按鈕特效不僅能提升用戶體驗,還能為網頁增添獨特的視覺吸引力。今天,我們將通過CSS來實現一個“表單制作代碼,登錄動畫背景前端模板”。該素材呈現了數據符號排版顯示出人形的動畫效果,新穎有…

    1分鐘用DeepSeek編寫一個PDF轉Word軟件

    一、引言 如今&#xff0c;在線工具的普及讓PDF轉Word成為了一個常見需求&#xff0c;常見的pdf轉word工具有收費的wps&#xff0c;免費的有pdfgear&#xff0c;見下文&#xff1a; PDFgear:一款免費的PDF編輯、格式轉化軟件-CSDN博客 還有網上在線的免費pdf轉word工具smallp…

    【找工作】C++和算法復習(自用)

    文章目錄 C頭文件自定義排序函數stl 算法數據結構樹狀數組 數學字符串manacherkmp 自用隨便記錄 C 排序 stl 頭文件 全能頭文件&#xff1a; #include<bits/stdc.h>自定義排序函數 bool compare(const int &odd1,const int &odd2) {return odd1>odd2; }…

    請談談 React 中的狀態管理,如何使用 Context API 和 Redux 進行狀態管理?

    一、Context API 深度應用 1. 核心實現原理 通過createContext創建上下文對象&#xff0c;使用Provider組件包裹需要共享狀態的組件樹&#xff0c;子組件通過useContext Hook或Consumer組件消費數據。 代碼示例&#xff08;主題切換場景&#xff09;&#xff1a; // 創建上…

    kotlin的Int類型調用toString()會導致空指針嗎

    DeepSeek R1 思考 38 秒 思考過程 用戶的問題是關于Kotlin中Int類型調用toString()是否會導致空指針異常。首先&#xff0c;我需要回憶Kotlin的基本類型系統。Kotlin中的Int屬于基本數據類型&#xff0c;但它在語言層面被表示為非空類型。也就是說&#xff0c;Int變量本身不能…

    網絡運維學習筆記 017 HCIA-Datacom綜合實驗01

    文章目錄 綜合實驗1實驗需求總部特性 分支8分支9 配置一、 基本配置&#xff08;IP二層VLAN鏈路聚合&#xff09;ACC_SWSW-S1SW-S2SW-Ser1SW-CoreSW8SW9DHCPISPGW 二、 單臂路由GW 三、 vlanifSW8SW9 四、 OSPFSW8SW9GW 五、 DHCPDHCPGW 六、 NAT缺省路由GW 七、 HTTPGW 綜合實…

    基于Hadoop的汽車大數據分析系統設計與實現【爬蟲、數據預處理、MapReduce、echarts、Flask】

    文章目錄 有需要本項目的代碼或文檔以及全部資源&#xff0c;或者部署調試可以私信博主 項目介紹爬蟲數據概覽HIve表設計Cars Database Tables 1. cars_data2. annual_sales_volume3. brand_sales_volume4. city_sales_volume5. sales_volume_by_year_and_brand6. sales_distri…

    springboot實現多文件上傳

    springboot實現多文件上傳 代碼 package com.sh.system.controller;import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.util.StringUtils; import org.springframework.web.bind.annotation.PostMap…

    Java所有運算符理解

    Java 運算符 算術運算符 表格中的實例假設整數變量A的值為10&#xff0c;變量B的值為20&#xff1a; 操作符描述例子加法 - 相加運算符兩側的值A B 等于 30-減法 - 左操作數減去右操作數A – B 等于 -10*乘法 - 相乘操作符兩側的值A * B等于200/除法 - 左操作數除以右操作數…

    紛析云:賦能企業財務數字化轉型的開源解決方案

    在企業數字化轉型的浪潮中&#xff0c;財務管理的高效與安全成為關鍵。紛析云憑借其開源、安全、靈活的財務軟件解決方案&#xff0c;為企業提供了一條理想的轉型路徑。 一、開源的力量&#xff1a;自主、安全、高效 紛析云的核心優勢在于其100%開源的財務軟件源碼。這意味著…