反射
-在計算機科學中,反射是指計算機程序在運行時(Run time)可以訪問、檢測和修改它本身狀態或行為的一種能力。[1]用比喻來說,反射就是程序在運行的時候能夠“觀察”並且修改自己的行為。
要注意術語“反射”和“內省”(type introspection)的關系。內省(或稱“自省”)機制僅指程序在運行時對自身信息(稱為元數據)的檢測;反射機制不僅包括要能在運行時對程序自身信息進行檢測,還要求程序能進一步根據這些信息改變程序狀態或結構。
C++的反射
C++的標准語法是不提供反射的特性的,不過隨着C++17的定稿,估計這個關鍵詞不會加到標准中了。不過這個我們可以用template來實現,這次就不寫template了。今天主要是講的是Qt。
Qt的反射
Qt最大的特點就是增加了moc的過程,個人理解,Qt擴展了C++的語法,以及增強了自己的基本庫。
Meta Object System is a part of Qt framework core provided to support Qt extensions to C++ like signals/slots for inter-object communication, run-time type information, and the dynamic property system.[1]
Architecture
The Meta object system consists of 3 things: QObject class, Q_OBJECT macro and a tool called moc (Meta-Object Compiler). QObject is the base class for all Qt classes, Q_OBJECT macro is used to enable meta-object features in classes and finally moc is a preprocessor that changes Q_OBJECT macro instances to C++ source code to enable meta object system mechanism in the class in which it is used.[2]
Using the meta object system has brought some criticism. In Qt documentation, several reasons have been given for the use of the meta object system, including benefits of code generation, dynamism of GUIs, automatic binding to scripting languages, not adding limitations and also reasonable performance in signal/slot implementation with moc.[3] There are some efforts to make Qt needless of a preprocessor. These efforts include re-implementing Qt moc using libclang.[4]
moc可以理解將Qt中的一些關鍵詞,比如Q_Object ,Q_PROPERTY等轉化為c++的基本語法,所以我們在編譯Qt的工程時,首先要qmake->make。
個人認為Qt有兩個我比較看重的特點。
- STL基礎庫的擴展。
- C++基本語法與特性的擴展。
尤其是基本語法的擴展,比如信號槽,元對象系統,讓C++一個靜態語言有了動態語言的特性(當然你也可以用template來實現,但是這玩意兒一般人又用不起,而且寫起來也比較惡心)。這一點我就可以吹爆了。
下邊開始講Qt的反射。Qt的反射是基於Qt的元對象系統的。當然Qt還不能做到像java那樣通過類名來創建一個對象,這個需要我們自己寫一個工廠模式。個人認為,除了這一點,與java中的反射沒有啥區別了。都可以動態的去訪問其成員變量,成員函數,以及設置屬性。當然必須加上Qt自己的語法。
舉個栗子
//1 .繼承 QObject class TestObject : public QObject { Q_OBJECT // 2.聲明Q_OBJECT Q_PROPERTY(QString text READ text WRITE setText NOTIFY textChange) //3. Q_PROPERTY 注冊成員變量 Q_PROPERTY(QString text MEMBER m_text NOTIFY textChange) //4. 注冊的成員變量能夠響應自定義的signals textChange public: TestObject(QObject* parent); ~TestObject(); void init(); //------ Q_INVOKABLE QString text(); //5.注冊類的成員函數 Q_INVOKABLE void setText(const QString& strText); //5.注冊類的成員函數 QString m_text; //類的成員變量 signals: void textChange(); //自定義的signals public slots: void textslot(){qDebug()<<"textslot"<<endl;} //自定義的signals響應的槽函數 }; //cpp TestObject::TestObject(QObject* parent) : QObject (parent) { this->setObjectName("TestObject"); connect(this, SIGNAL(textChange()),this, SLOT(textslot())); } TestObject::~TestObject() { } void TestObject::init() { qDebug()<<"*********************"<<endl; qDebug()<<"init"<<endl; } QString TestObject::text() { return m_text; } void TestObject::setText(const QString& strText) { if (m_text == strText) return; m_text = strText; // emit textChange(); //有了第四條這個語句已經不需要了。 }
這里看調用方法
TestObject* obj = new TestObject(this); // new一個對象 qDebug()<<obj->objectName()<<endl; //輸出對象的名字 // custom object property obj->setProperty("text", "hahaha"); //設置對象的屬性 qDebug()<<obj->property("text").toString()<<endl; //輸出對象的屬性 //得到注冊的類成員函數 qDebug()<<"begin--------------------custom class method"<<endl; const QMetaObject* mobj = obj->metaObject(); qDebug()<<mobj->methodCount()<<endl; for(int i = 0; i < mobj->methodCount(); i++) { QMetaMethod mMethod = mobj->method(i); QByteArray byteArray = mMethod.name(); //輸出函數類型與函數名稱 qDebug()<<mMethod.typeName()<<"->"<<QString(byteArray)<<endl; } qDebug()<<"end----------------------custom class method"<<endl; //調用注冊的成員函數,通過Q_RETURN_ARG來獲取返回值 qDebug()<<"begin QMetaObject::invokeMethod"<<endl; QString invokeString; //調用類的成員函數 QMetaObject::invokeMethod(obj, "text", Qt::DirectConnection, Q_RETURN_ARG(QString, invokeString)); qDebug()<<invokeString<<endl; qDebug()<<"end QMetaObject::invokeMethod"<<endl; // 再次設置text值,可以響應這個信號,可以參考4 obj->setProperty("text", "luelueluelue");
看上邊的代碼,C++能夠隨時獲取當前類的成員變量與成員函數以及調用。(並不是通過C++的type id)這就是反射。反射在寫GUI的時候是非常有用的。也就是我們可以隨時獲取當前對象的任何我們想要的屬性以及想要調用的函數。靜態語言有了動態語言的特性。這就是Qt強大的地方。
Qt通過類名來實現真正的反射
#include <QByteArray> #include <QMetaObject> #include <QHash> #ifndef OBJECTFACTORY_H #define OBJECTFACTORY_H class ObjectFactory { public: template<typename T> static void registerClass() { constructors().insert( T::staticMetaObject.className(), &constructorHelper<T> ); } static QObject* createObject( const QByteArray& className, QObject* parent = NULL ) { Constructor constructor = constructors().value( className ); if ( constructor == NULL ) return NULL; return (*constructor)( parent ); } private: typedef QObject* (*Constructor)( QObject* parent ); template<typename T> static QObject* constructorHelper( QObject* parent ) { return new T( parent ); } static QHash<QByteArray, Constructor>& constructors() { static QHash<QByteArray, Constructor> instance; return instance; } }; #endif // OBJECTFACTORY_H
下邊是使用方法。todo ,這個東西應該寫成單例模式的。
//使用方法 ObjectFactory fac; fac.registerClass<TestObject>(); qDebug()<<"begin-------------------------------"<<endl; TestObject* object = qobject_cast<TestObject*>(fac.createObject( "TestObject" , this)); object->setText("template factory"); qDebug()<<object->text()<<endl;