【QT】メタオブジェクトシステム学習記(1)

01. メタオブジェクトシステム

メタオブジェクト システムは、Qt をオリジナルの C++ に拡張したもので、主にシグナルとスロットのメカニズムを実装するために導入されました。シグナルとスロットのメカニズムは Qt のコア機能です。

Qt のメタオブジェクト システムが提供する機能は、
1. オブジェクト間の通信のためのシグナルとスロットのメカニズム
2. 実行時の型情報と動的属性システム などです。

メタオブジェクト システムの機能を使用するには、3 つの前提条件を満たす必要があります。

  1. このクラスは QObject クラスを継承する必要があります。
  2. Q_OBJECTメタオブジェクト プロパティを有効にするために使用されるクラス宣言のプライベート領域にマクロを追加する必要があります。その後、動的プロパティ、シグナル、スロットなどの関数を使用できるようになります。
  3. メタオブジェクト コンパイラ (moc) は、各 QObject サブクラスにメタオブジェクト機能の実装に必要なコードを提供します。
1.1. メタオブジェクトの動作原理
  • メタオブジェクト システムは C++ の拡張であるため、メタオブジェクト システムが有効になっている Qt プログラムは、従来のコンパイラを使用して直接コンパイルすることはできません。Qt プログラムをコンパイルする前に、拡張された構文を削除する必要があります。この関数は moc 関数です。やる事。
  • moc の完全な名前は: Meta-Object Compiler (メタオブジェクト コンパイラ). これはツール (qmake に似ています). このツールは C++ ソース ファイルを読み取って分析します. マクロを含む 1 つ以上のクラス宣言が見つかった場合、別Q_OBJECTQ_OBJECTマクロ実装コードを含む C++ ソース ファイルが生成されます (ソース ファイルの名前は通常 moc_*.cpp です)。この新しいソース ファイルはクラスのソース ファイルに #include されるか、コンパイルされて復号化されてクラスのソース ファイルに組み込まれます。実装 (通常はこのアプローチが使用されます)。注: 新しいファイルは古いファイルを「置き換え」ませんが、同じソース ファイルが一緒にコンパイルされます。

その他の概念:

1. メタオブジェクト コード: moc ツールによって生成されたソース ファイルのコードを指し、マクロQ_OBJECT実装コードが含ま
れています。 2. moc ツールのパスは通常、C:\app のようにインストール パスにあります。 \Qt5.8.0MinGw\5.8 \mingw53_32\bin

1.2、 Q_OBJECT宏

まず、マクロが含まれている画像を確認してからQ_OBJECT、F2 キーを押すと入力できます。マクロが宣言されたクラスにいくつかのメンバーを追加していることがわかります。赤いボックスは仮想関数のメンバーをマークしています (これらの仮想関数は定義されていないことに注意してください)。C++ 構文によれば、仮想関数は純粋な仮想関数として定義または宣言される必要があります
ここに画像の説明を挿入します
Q_OBJECT関数、moc ツール そのジョブの 1 つは、上記のメンバーの定義を生成し、その他の必要なコードも生成することです。

1.3. Qt Creator がメタオブジェクト システムを開始する

Q_OBJECTmoc ツールは Qt Creator を通じて使用されるため、mocがプロジェクト内のマクロを含むクラスを検出して処理できることを確認する必要があるため、次のルールに従う必要があります:
1. QObject から派生したマクロを含むクラスの定義は、 Q_OBJECT2.プロジェクト内のすべてのソース ファイル (SOURCES 変数) とヘッダー ファイル (HEADERS 変数) がファイルにリストされていることを
確認します; 3. ヘッダー ファイル内で ( ) などのロジック命令を使用して、ヘッダーを防ぐ必要があります。ファイルが複数回インクルードされることを防ぎます。4. QObject クラスは、基本クラス リストの最初のクラスである必要があります。pro
#ifndef、#define、#endif


上記のルールから、Qt Creator を使用してコードを記述するときは、クラスをヘッダー ファイルに定義し、メンバー関数の定義をソース ファイルに配置する必要があることがわかります (これにより、プログラムはこのように書かれています
、面倒ではありますが、コードを整理するには良い方法です。

注: QObject クラスの派生クラスが定義およびビルドされている場合 (ビルドは追加されませんがO_OBJECT、ビルドの完了後に追加されます)、この時点で qmake コマンドを再度実行する必要があります。そうしないと、moc はコードを生成できません。

この本の原文は次のとおりです:
ここに画像の説明を挿入します
上記のプログラムを実行すると、デバッグ ディレクトリに moc_m.cpp ソース ファイルが見つかります。このソース ファイルは
moc ツールを使用して生成されます。ソース ファイル内のコードはメタオブジェクト コードです読者はそのコードを閲覧できます。このディレクトリに moc_m.cpp ファイルが存在しない場合は
、moc ツールが正常に起動できていないため、Qt Creator で qmake コマンドを実行してプログラムをビルドする必要があります

1.4. コマンドライン起動メタオブジェクト (一般的には使用されません)
  1. コマンドラインで moc ツールを使用し、moc ツールによって生成された cpp ファイルをソース ファイルに含める必要があります。
  2. 現時点では、Q_OBJECT を含むクラスをヘッダー ファイルに配置する必要はありませんが、m.cpp ファイルに配置されていると仮定すると、内容は次のようになります。
#include<QObject>
class A : public QObject
{
    
      
	Q_OBJECT
public:
  A(){
    
    }  
};

int main(int argc, char *argv[]) {
    
    
 	A ma; return 0; 
}
  1. Qt 5.8 for Desktop (MinGW 5.3.0 32 ビット) コマンド ライン ツールを開き、次のコマンドを入力します。
moc d:\qt\m.cpp -o d:\qt\mm.cpp

上記のコマンドは、m.cpp に基づいて mm.cpp ファイルを生成します。mm.cpp ファイル内のコードはメタオブジェクト コードです。ここで、
m.cpp と mm.cpp は両方とも d:\qt フォルダーにあります。

  1. 次に、次のように m.cpp を再度開き、#include を使用して mm.cpp をインクルードします。
#include <QObject>
//#include "mm.cpp"   // 不能把mm.cpp包含在类A的定义之前
class A : public QObject {
    
    
	Q_OBJECT
public:
	A(){
    
    }
};

#include "mm.cpp"  // 必须把mm.cpp包含在类A的定义后面,因为mm.cpp源文件中有对A的成员定义,此时必须见到类A的完整定义。

int main(int argc, char* argv[]) {
    
    
	A ma;
	return 0;
}
  1. 次に、qmake を使用してプロジェクト ファイルと makefile ファイルを生成し、mingw32-make コマンドを使用します。

一般に、QT 開発者がコマンド ライン メソッドを使用することはめったにありません。これは、Qt Creator がすでにコマンド ライン メソッドを実行しているためです。私は、この本で言及されているのを見つけて、ここに記録しました。

02. 反射機構

リフレクション モード (リフレクション モード/リフレクション メカニズム): 実行時に任意のクラス オブジェクトのすべての型情報、属性、メンバー関数、およびその他の情報を取得できるメカニズムを指します。

メタオブジェクト システムとリフレクション メカニズム:
1. メタオブジェクト システムによって提供される機能の 1 つは、実行時の型情報と QObject 派生クラス オブジェクトのデータ
メンバープログラムは、 QObject 派生クラス オブジェクトが属する
クラスの名前、親クラスの名前、オブジェクトのメンバー関数、列挙型、データ メンバー、その他の情報を取得できます。実際、これが
リフレクション メカニズムです。

2. Qt のメタオブジェクト システムは QObject から継承する必要があり、リフレクション メカニズムの主な役割がわかるため、Qt の
メタオブジェクト システムは主に QObject クラス オブジェクトとその派生クラス オブジェクトに関する情報をプログラムに提供します。 、
QObject から派生したクラス オブジェクトではないため、Qt のメタオブジェクト システムを使用してこの情報を取得することはできません

メタオブジェクトの具体的な説明: 別のオブジェクトの構造を記述するために使用されるオブジェクトを指します。

具体的にプログラムに直すと以下のようになります。

class B
{
    
    
	//TODO....
};

class A : public B
{
    
    
	B mb;
};

クラス A によって作成されたオブジェクトを記述するために mb が使用されると仮定すると、mb はメタオブジェクトです。

2.1. Qt はリフレクションメカニズムを実装します
  1. Qt は一連のクラスを使用してリフレクション メカニズムを実装します。これらのクラスはオブジェクトの各側面を記述します。クラスはQMetaObjectQObject とその派生クラス オブジェクトのすべてのメタ情報を記述します。このクラスは Qt メタオブジェクト システムのコア クラスですこのクラスのメンバー関数は、QObject とその派生クラスのオブジェクトのすべてのメタ情報を取得できるため、QMetaObjectクラスのオブジェクトは Qt におけるメタオブジェクトであると言えます。注:QMetaObjectクラス内のメンバー関数を呼び出すには、QMetaObject型のオブジェクトを使用する必要があります。
  2. オブジェクトのメンバーを説明します。オブジェクトには、データ メンバー、関数メンバー、コンストラクター、列挙メンバーなどが含まれます。Qt では、これらのメンバーはさまざまなクラスを使用して記述されます。たとえば、関数のメンバーはクラスQMetaMethodと属性を使用してQMetaProperty記述されます。最後に、QMetaObject クラスを使用してクラス オブジェクト全体を記述します。たとえば、メンバー関数の変数名を取得します。
QMetaMethod qm = metaObject->method(1);  // 获取索引为1的成员函数
qDebug()<< qm.name() << "\n";  // 输出该成员函数的名称

Qt のリフレクション メカニズムを使用するための前提条件:

  1. これは QObject クラスから継承する必要があり、Q_OBJECT マクロをクラスに追加する必要があります。
  2. メンバ関数の登録: 通常のメンバ関数を反映させたい場合は、関数宣言の前にQObject::Q_INVOKABLEマクロを追加する必要があります。
  3. メンバー変数の登録: メンバー変数を反映させたい場合はマクロを使用する必要がありますQ_PROPERTY

実際QObject::Q_INVOKABLEQ_PROPERTY私は Qt5 の QQuick のこれら 2 つのマクロを深く理解しています。QML が C++ のプロパティとメソッドを使用したい場合、これらのマクロをマークする必要があります。

Qt リフレクション メカニズムの実装原理の簡単な説明:

  1. Q_OBJECTマクロ展開後、型へのポインターmeteObject()を返す仮想メンバー関数があり、そのプロトタイプは次のとおりです。QMetaObject
virtual const QMetaObject* metaObject() const;

メタオブジェクト システムを有効にするクラスにはすべてQ_OBJECTマクロが含まれているため、これらのクラスにはすべて仮想メンバー関数が含まれており、 metaObject()function によって返されたポインターを介してQMetaObjectクラス内のメンバー関数を呼び出すことで、QObject とその派生クラス オブジェクトに関するさまざまな情報をクエリできます。 。

  1. Qt の moc は次の作業を完了します:
    1.Q_OBJECTマクロ展開後に宣言されたメンバー関数の実装コードを生成します;
    2. Qt の特別なキーワードとマクロを識別します ( Q_PROPRTYQ_INVOKABLEslotなどの識別signals) 。
2.2. クラス内のメンバー関数に関する情報を取得するためのリフレクション機構
2.1.1、QMetaMethon クラス

関数: オブジェクトのメンバー関数を記述するために使用されます。このクラスのメンバー関数を使用して、オブジェクトのメンバー関数に関する情報を取得できます。

よく使用されるメンバーをいくつかリストします。

// 此枚举用于描述函数的类型,即:普通成员函数、信号、槽、构造函数
enum MethodType {
    
    Method, Signal, Slot, Constructor}

// 此枚举主要用于描述函数的访问级别(私有的、受保护的、公有的)
enum Access {
    
    Private,Protected,Public}

// 返回函数的签名(qt5.0)
QByteArray methodSignature() const;

// 返回函数的类型(信号、槽、成员函数、构造函数)
MethodType methodType() const;

// 返回函数的名称(qt5.0)
QByteArray name() const;

// 返回函数的参数数量(qt5.0)
int parameterCount() const;

// 返回函数参数名称的列表
QList<QByteArray> parameterNames() const;

// 返回指定索引处的参数类型,返回值是使用QMetaType注册的类型,若类型未注册,则返回值为QMetaType::UnknownType。
int parameterType(int index) const;

// 返回函数参数类型的列表
QList<QByteArray> parameterTypes() const;

// 返回函数的返回类型。返回值是使用QMetaType注册的类型,若类型未注册,则返回值为QMetaType::UnknownType。
int returnType() const;

// 返回函数的返回类型的名称
const char* typeName() const;

// 返回函数的访问级别(私有的、受保护的、公有的)
Access access() const;
2.1.2. QMetaObject クラス

機能: クラスに関するメタオブジェクト情報を提供するために使用されます。

よく使用されるメンバーをいくつかリストします。

/* 返回名为 f 的函数的索引号,否则返回-1。此处应输入正确的函数签名,比如函数形
式为 void f(int i,int j);则正确的形式为 xx.indexOfMethod("f(int,int"); 以下形式都不是
正确的形式,"f(int i, int j)"、"void f(int, int)"、 "f"、"void f"等。*/
int indexOfMethod(const char* f) const;

// 返回信号 s 的索引号,否则返回-1,若指定的函数存在,但不是信号,仍返回-1。
int indexOfSignal(const char * s) const;

// 返回构造函数 c 的索引号,否则返回-1
int indexOfConstructor(const char *c) const;

// 返回构造函数的数量。
int constructorCount() const ; 

// 返回指定索引 i 处的构造函数的元数据。
QMetaMethod constructor(int i)const;

// 返回函数的数量,包括基类中的函数、信号、槽和普通成员函数。
int methodCount() const;

// 返回父类中的所有函数的总和,也就是说返回的值是该类中的第一个成员函数的索引位置。
int methodOffset() const;

// 返回指定索引 i 处的函数的元数据。
QMetaMethod method(int i) const;

以下は参考のためにのみ書籍からの例です

#include "m.h"
#include <QMetaMethod>
#include <QByteArray>
#include <iostream>
using namespace std;
int main(){
    
      A ma; B mb; //创建两个对象
const QMetaObject *pa=ma. metaObject ();
const QMetaObject *pb=mb. metaObject ();
//以下为通过 QMetaObject 的成员函数获取的信息。
int j=pa->methodCount(); /*返回对象 ma 中的成员函数数量,包括从父类 QObject 继承而来的 5 个
成员函数及本对象中的 2 个成员函数(注意,不包括 g1)、1 个信号,所以
总数为 8。*/
cout<<j<<endl; //输出 8
int i=pa->indexOfMethod("g(int,float)"); //获取对象 ma 中的成员函数 g 的索引号。
cout<<i<<endl; //输出 7
i=pa->constructorCount(); //获取对象 ma 所属类中的构造函数的数量。
cout<<i<<endl; //输出 2
i=pb->constructorCount(); /*获取对象 mb 所属类 B 中的构造函数的数量,因类 B 无构造函数,所以
返回值为 0,此处也可看到,构造函数数量不包含父类的构造函数*/
cout<<i<<endl; //输出 0。
i=pa->indexOfConstructor("A(int)"); //获取对象 ma 所属类中的构造函数 A(int)的索引号。
cout<<i<<endl; //输出 1。
i=pa->indexOfSignal("gb3()"); //获取对象 ma 的信号 gb3()的索引号。
cout<<i<<endl; //输出 5。
i=pa->indexOfSignal("f()"); /*获取对象 ma 的信号 f()的索引号。因为成员函数 f 存在,但不是信
号,所以返回值为-1。*/
cout<<i<<endl; //输出-1。
i=pb->methodOffset(); /*获取父类的成员函数数量,包括父类A及QObject中的成员函数,总共为8。
*/
cout<<i<<endl; //输出 8,此处相当于是对象 mb 自身成员函数开始处的索引号。
//以下为通过 QMetaMethon 的成员函数获取的信息。
//获取对象 ma 的成员函数 g 的元数据。
QMetaMethod m=pa->method(pa->indexOfMethod("g(int,float)"));
QByteArray s= m.name(); //获取成员函数 g 的函数名。
cout<<s.data()<<endl; //输出 g
s=m.methodSignature(); //获取函数 g 的签名
cout<<s.data()<<endl; //输出 g(int,float)
i=m.methodType(); /*获取函数 g 的类型,此处返回的是 QMetaMethod::MethodType 中定义的枚举值,
其中 Method=0,表示类型为成员函数*/
cout<<i<<endl; //输出 0(表示成员函数)。
//以下信息与函数的返回类型有关
s=m.typeName(); //获取函数 g 的返回值的类型名
cout<<s.data()<<endl; //输出 void
i=m.returnType(); /*获取函数 g 返回值的类型,此处的类型是 QMetaType 中定义的枚举值,其中枚举
值 QMetaType::void=43*/
cout<<i<<endl; //输出 43
//以下信息与函数的参数有关
i=m.parameterType(1); /*获取函数 g 中索引号为 1 的参数类型,此处的类型是 QMetaType 中定义的
枚举值,其中枚举值 QMetaType::float=38*/
cout<<i<<endl; //输出 38
QList<QByteArray> q=m.parameterNames(); //获取函数 g 的参数名列表
cout<<q[0].data()<<q[1].data()<<endl; //输出 ij
q=m.parameterTypes(); //获取函数 g 的参数类型列表。
cout<<q[0].data()<<q[1].data()<<endl; //输出 intfloat
return 0; }
2.3. クラス関連情報を取得するためのリフレクション機構
  1. クラス関連の情報を取得するための QMetaObject クラスのメンバー関数は次のとおりです。
/*获取类的名称,注意,若某个 QObject 的子类未启动元对象系统(即未使用 Q_OBJECT
宏),则该函数将获取与该类最接近的启动了元对象系统的父类的名称,而不再返回
该类的名称,因此建议所有的 QObject 子类都使用 Q_OBJECT 宏。
*/
const char* className() const;

// 返回父类的元对象,若没有这样的对象则返回0
const QMetaObject* superClass() const;

// 若该类继承自mo描述的类型,则返回true,否则返回false。类被认为继承自身。
bool inherits(const QMetaObject* mo) const;  // (Qt5.7)
  1. クラス関連の情報を取得するための QObject クラスのメンバー関数は次のとおりです。
// 若该类是className指定的类的子类则返回true,否则返回false。类被认为继承自身
bool inherits(const char* className) const;

以下に例を示します。

ヘッド ファイル:

#ifndef M_H //要使用元对象系统,需在头文件中定义类。
#define M_H
#include<QObject>
class A:public QObject{
    
     Q_OBJECT};
class B:public A{
    
     Q_OBJECT};
class C:public QObject{
    
    Q_OBJECT};
class D:public C{
    
    };
#endif // M_H

ソースファイル:

#include "m.h"
#include <QMetaMethod>
#include <iostream>
using namespace std;
int main(){
    
      A ma; B mb; C mc; D md;
const QMetaObject *pa=ma. metaObject ();
const QMetaObject *pb=mb. metaObject ();
cout<<pa->className()<<endl; //输出类名 A
//使用 QMetaObject::inherits()函数判断继承关系。
cout<<pa->inherits(pa)<<endl; //输出 1,类被认为是自身的子类
cout<<pa->inherits(pb)<<endl; //输出 0,由 pb 所描述的类 B 不是类 A 的子类。
cout<<pb->inherits(pa)<<endl; //输出 1,由 pa 所描述的类 A 是类 B 的子类。
//使用 QObject::inherits()函数判断继承关系。
cout<<ma.inherits("B")<<endl; //输出 0,类 A 不是类 B 的子类。
cout<<ma.inherits("A")<<endl; //输出 1,类被认为是自身的子类
cout<<md.inherits("D")<<endl; //输出 0,因为类 D 未启动元对象系统。
cout<<md.inherits("C")<<endl; /*输出 1,虽然类 D 未启动元对象系统,但类 C 已启动,此种情形下
能正确判断继承关系。*/
cout<<md. metaObject ()->className()<<endl; /*输出 C,此处未输出 D,因为类 D 未启动元对象系统,
与类 D 最接近的启动了元对象系统的父类是 C,因此返回 C。*/
return 0; }
  1. qobject_cast 関数は次の構文を使用します。
DestType* qobject_cast<DestType*>(QObject* p);


1. この関数はC++のdynamic_castに似ていますが、実行速度はdynamic_castより速く、 C++のRTTIのサポートは必要ありませんが、 qobject_castはQObjectとその派生クラスにのみ適用できます。
2. 主な関数は、ソース型 QObject を山括弧内のターゲット型 DesType (またはサブタイプ) に変換し、
ターゲット型へのポインタを返すことです。変換が失敗した場合は 0 が返されます。または、ソース型 QObject がターゲット
型 DestType (またはそのサブタイプ) に属している場合は、ターゲット型へのポインタが返され、それ以外の場合は 0 が返されます。
3. qobject_cast を使用するための条件: ターゲット型 DestType は QObject から (直接的または間接的に) 継承し、
Q_OBJECT マクロを使用する必要があります。

例:

ヘッド ファイル:

#ifndef M_H //要使用元对象系统,需在头文件中定义类。
#define M_H
#include<QObject>
#include <iostream>
using namespace std;
class A:public QObject{
    
     Q_OBJECT
public:void fa(){
    
    cout<<"FA"<<endl;}  };
class B:public A{
    
     Q_OBJECT
public:void fb(){
    
    cout<<"FB"<<endl;}  };
class C:public QObject{
    
    Q_OBJECT
public:void fc(){
    
    cout<<"FC"<<endl;}  };
class D:public C{
    
     public: void fd(){
    
    cout<<"FD"<<endl;} };
#endif // M_H

ソースファイル:

#include "m.h"
#include <QMetaMethod>
#include <iostream>
using namespace std;
//qobject_cast 的简单应用(类型判断)
void g(QObject *p){
    
    
if(qobject_cast<A*>(p)) //若 p 是类 A 及其派生类类型
{
    
    cout<<"GA"<<endl;}
if(qobject_cast<B*>(p))//若 p 是类 B 及其派生类类型
{
    
    cout<<"GB"<<endl;}
else //若 p 不是类 B 及其派生类类型
cout<<"XX"<<endl;  }
int main(){
    
      A *pa=new A; B *pb=new B; C *pc=new C; D *pd=new D;
qobject_cast<B*>(pa)->fb(); //输出 FB,转换成功后可调用子类中的函数。
//qobject_cast<D*>(pc); //错误,因为类 D 未使用 Q_OBJECT 宏。
g(pa); //输出 GA、XX。因为 pa 不是 B 及其派生类类型所以会输出 XX。
g(pb); //输出 GA、GB。因为 pb 是 A 的派生类类型,所以首先输出 GA,然后输出 GB。
g(pc); //输出 XX,因为 pc 即不是 A 也不是 B 及其派生类的类型,所以输出 XX。
g(pd); //输出 XX,原因同上。
return 0; }

つづく!

おすすめ

転載: blog.csdn.net/m0_43458204/article/details/131765616