如何在C++中實現反射機制,應該算是C++開發中經常遇到的問題之一。C++程序沒有完整的元數據,也就無法實現原生的反射機制。從性能的角度講,這樣的設計不難理解,畢竟在運行時儲存這些元數據需要額外的開銷。不為你不使用的東西付出代價,這是C++的哲學,所以當我們需要反射機制時,我們得自己來實現它。所幸如今各種C++的反射實現已經相當成熟,比如boost::reflect,以及本文所使用的Qt。
Qt是常見的C++跨平台應用程序框架之一,除了用於開發GUI程序之外,Qt本身也是一套完整的C++庫。不同於boost這樣的模板庫,Qt利用自帶的Meta-Object Compiler(moc)來生成額外的C++代碼,這些代碼實現了Qt程序所必須的元數據對象。Qt中很多特有的機制,比如signals/slots,都依賴於Qt的元數據對象,可以說Qt是基於C++的一種擴展。以下我們來看兩個例子,一個使用了Qt元數據對象,另一個則不使用,同樣實現函數的動態調用。
首先我們來看如何使用Qt的元數據對象,我們定義了一個Service類,用來存取配置信息。首先來看頭文件service.h:
#ifndef SERVICE_H #define SERVICE_H #include <QObject> #include <QString> #include <QVariantMap> class Service : public QObject { Q_OBJECT public: QVariantMap process(const QVariantMap &request); private: // request: // "cmd" : "set_config" // "key" : keyname // "value" : QVariant // reply: // "error" : error message Q_INVOKABLE QVariantMap process_set_config(const QVariantMap &request); // request: // "cmd" : "get_config" // "key" : keyname // reply: // "error" : error message // "value" : QVariant Q_INVOKABLE QVariantMap process_get_config(const QVariantMap &request); // request: // "cmd" : "get_json" // reply: // "error" : error message // "json" : utf8 json Q_INVOKABLE QVariantMap process_get_json(const QVariantMap &request); // "key1" : QVariant // "key2" : QVariant // ... QVariantMap m_settings; }; #endif // SERVICE_H
這個類很簡單,對外提供一個public的process函數,這個函數接受一個QVariantMap作為request,並返回一個QVariantMap作為reply。QVariantMap等於QMap<QString, QVariant>,我們用它作為萬能參數。Service類內部有多個private函數,都以process開頭,用來處理不同的request。我們接下來演示如何根據輸入的request動態調用這些處理函數。
我們注意到Service類繼承自QObject,並在類開頭聲明了Q_OBJECT宏。有了這個宏,moc會自動生成moc_service.cpp,Qt開發者對此應該很熟悉了,這里不再贅述。注意類中的幾個處理函數之前都添加了Q_INVOKABLE宏,Qt會自動將這些函數注冊到元數據對象中。如果不使用Q_INVOKABLE宏,我們也可以將這些處理函數聲明為slots。除此之外,普通成員函數是無法被元數據對象調用的。
再看service.cpp:
#include "service.h" #include <QtCore> QVariantMap Service::process(const QVariantMap &request) { QVariantMap reply; QString cmd = request["cmd"].toString(); if (cmd.isEmpty()) { reply["error"] = "invalid command"; return reply; } QString methodName = QString("process_%1").arg(cmd); bool bret = metaObject()->invokeMethod(this, methodName.toLatin1(), Q_RETURN_ARG(QVariantMap, reply), Q_ARG(QVariantMap, request) ); if (bret) { // printf("\nProcess finished.\n"); } else { reply["error"] = "no available method"; } return reply; } QVariantMap Service::process_set_config(const QVariantMap &request) { QVariantMap reply; reply["error"] = "success"; QString keyname = request["key"].toString(); if (keyname.isEmpty()) { reply["error"] = "invalid keyname"; return reply; } m_settings[keyname] = request["value"]; return reply; } QVariantMap Service::process_get_config(const QVariantMap &request) { QVariantMap reply; reply["error"] = "success"; QString keyname = request["key"].toString(); if (keyname.isEmpty()) { reply["error"] = "invalid keyname"; return reply; } if (m_settings.contains(keyname)) { reply["value"] = m_settings[keyname]; return reply; } reply["error"] = "key not found"; return reply; } QVariantMap Service::process_get_json(const QVariantMap &) { QVariantMap reply; reply["error"] = "success"; QJsonObject jObj = QJsonObject::fromVariantMap(m_settings); QJsonDocument jDoc(jObj); reply["json"] = jDoc.toJson(); return reply; }
可以看到process函數通過request["cmd"]得到request command,再在command之前加上"process_"前綴得到處理函數的名字。比如command為"set_config",則相應的處理函數名為"process_set_config"。之后程序再通過QMetaObject::invokeMethod來調用對應的處理函數。代碼中methodName.toLatin1()是將Unicode的QString字符串轉換為ASCII編碼的C字符串。
之前我們利用Q_INVOKABLE宏將處理函數注冊到元數據對象中,使得我們可以透過函數名來調用這些處理函數。函數的參數和返回值分別用Q_ARG和Q_RETURN_ARG宏進行了包裝。最后看main.cpp:
#include "service.h" #include <QtCore> int main() { Service service; QTextStream os(stdout); QVariantMap request1; request1["cmd"] = "set_config"; request1["key"] = "search-engine"; request1["value"] = "www.google.com"; service.process(request1); QVariantMap request2; request2["cmd"] = "set_config"; request2["key"] = "proxy"; request2["value"] = "192.168.100.1"; service.process(request2); QVariantMap request3; request3["cmd"] = "get_config"; request3["key"] = "proxy"; QVariantMap reply3 = service.process(request3); os << "\nproxy: " << reply3["value"].toString() << endl; QVariantMap request4; request4["cmd"] = "get_json"; QVariantMap reply4 = service.process(request4); os << "\njson:\n" << reply4["json"].toByteArray() << endl; return 0; }
程序本身並沒有直接調用處理函數,而是根據輸入的request command得到處理函數的名字,再利用元數據對象調用真正的處理函數。這樣如果需要添加對新的request command的支持,我們只需要編寫新的處理函數,而現有的程序邏輯則無需修改。
程序運行結果:
proxy: 192.168.100.1 json: { "proxy" : "192.168.100.1", "search-engine": "www.google.com" }
以上是利用Qt實現C++反射的一個簡單例子,使用了Qt元數據對象。Qt元數據對象需要moc生成額外的C++代碼,我們再來看如何不使用元數據對象實現C++反射。
同樣是Service這個類,我們來看頭文件service.h:
#ifndef SERVICE_H #define SERVICE_H #include <QObject> #include <QVariantMap> class Service : public QObject { public: Service(); QVariantMap process(const QVariantMap &request); private: // request: // "cmd" : "set_config" // "key" : keyname // "value" : QVariant // reply: // "error" : error message QVariantMap process_set_config(const QVariantMap &); // request: // "cmd" : "get_config" // "key" : keyname // reply: // "error" : error message // "value" : QVariant QVariantMap process_get_config(const QVariantMap &); // request: // "cmd" : "get_json" // reply: // "error" : error message // "json" : utf8 json QVariantMap process_get_json(const QVariantMap &); // "key1" : QVariant // "key2" : QVariant // ... QVariantMap m_settings; }; #endif // SERVICE_H
和之前的例子基本一樣,但是沒有聲明Q_OBJECT宏,沒有這個宏,Qt就不會用moc生成moc_service.cpp。本例無需再為處理函數加上Q_INVOKABLE宏。為了管理這些處理函數,我們需要額外定義一個模板類。來看handler.h:
#ifndef HANDLER_H #define HANDLER_H #include <QObject> #include <QString> #include <QVariantMap> template <typename _type> class EventHandler : public QObject { public: typedef QVariantMap (_type::*HandlerFuncType)(const QVariantMap &); // always use this function to register new handler objects // this function will check if all parameters are valid or not static bool AddHandler(QObject *parent, const QString &name, EventHandler<_type>::HandlerFuncType function) { if (!parent || !function || name.isEmpty()) return false; EventHandler<_type> *handler = new EventHandler<_type>(name, function); if (!handler) return false; handler->setParent(parent); // event handler objects are automatically deleted when their parent is deleted return true; } EventHandler<_type>::HandlerFuncType function() const { return m_function; } private: // disable public constructor EventHandler(const QString &name, EventHandler<_type>::HandlerFuncType function) : m_function(function) { this->setObjectName(name); } EventHandler<_type>::HandlerFuncType m_function; }; #endif // HANDLER_H
EventHandler繼承自QObject類,QObject擁有children屬性,一個QObject對象可以有多個QObject對象作為自己的children,代碼中handler->setParent(parent)正是將EventHandler對象設為parent對象的child。在Qt中我們可以很方便地管理QObject對象,每一個對象都有自己的名字,使得我們可以透過名字找到對應的對象。每一個EventHandler對象都有一個指向特定成員函數的指針。調用function方法將返回該函數指針的值。
再看Service類的實現service.cpp:
#include "service.h" #include "handler.h" #include <QtCore> typedef EventHandler<Service> ServiceHandler; #define AddServiceHandler(parent, func) ServiceHandler::AddHandler(parent, #func, &Service::func) Service::Service() { AddServiceHandler(this, process_set_config); AddServiceHandler(this, process_get_config); AddServiceHandler(this, process_get_json); } QVariantMap Service::process(const QVariantMap &request) { QVariantMap reply; QString cmd = request["cmd"].toString(); if (cmd.isEmpty()) { reply["error"] = "invalid command"; return reply; } QString handlerName = QString("process_%1").arg(cmd); ServiceHandler *handler = this->findChild<ServiceHandler *>(handlerName, Qt::FindDirectChildrenOnly); if (!handler) { reply["error"] = "no available handler"; return reply; } return ((*this).*(handler->function()))(request); } QVariantMap Service::process_set_config(const QVariantMap &request) { QVariantMap reply; reply["error"] = "success"; QString keyname = request["key"].toString(); if (keyname.isEmpty()) { reply["error"] = "invalid keyname"; return reply; } m_settings[keyname] = request["value"]; return reply; } QVariantMap Service::process_get_config(const QVariantMap &request) { QVariantMap reply; reply["error"] = "success"; QString keyname = request["key"].toString(); if (keyname.isEmpty()) { reply["error"] = "invalid keyname"; return reply; } if (m_settings.contains(keyname)) { reply["value"] = m_settings[keyname]; return reply; } reply["error"] = "key not found"; return reply; } QVariantMap Service::process_get_json(const QVariantMap &) { QVariantMap reply; reply["error"] = "success"; QJsonObject jObj = QJsonObject::fromVariantMap(m_settings); QJsonDocument jDoc(jObj); reply["json"] = jDoc.toJson(); return reply; }
不同於利用Qt元數據對象,現在我們需要在構造函數中手動添加所有的處理函數,當一個QObject對象析構時,它所有的children都會自動被釋放,所以我們無需顯式地delete這些EventHandler對象。在process函數中,通過QObject::findChild這個函數,我們能獲得handlerName對應的EventHandler對象,再通過EventHandler對象中的函數指針訪問真正的處理函數。
相比上一個例子利用Qt元數據對象,在本例中我們可以手動注冊一個方法的別名,比如將Service類的構造函數改為如下:
Service::Service() { AddServiceHandler(this, process_set_config); AddServiceHandler(this, process_get_config); AddServiceHandler(this, process_get_json); ServiceHandler::AddHandler(this, "process_set_setting", &Service::process_set_config); ServiceHandler::AddHandler(this, "process_get_setting", &Service::process_get_config); }
我們分別為Service::process_set_config和Service::process_get_config處理函數添加了別名process_set_setting和process_get_setting,之后可以用set_setting和get_setting兩個命令進行調用。我們稍微修改main.cpp:
#include "service.h" #include <QtCore> #include <cstdio> int main() { Service service; QTextStream os(stdout); QVariantMap request1; request1["cmd"] = "set_setting"; request1["key"] = "search-engine"; request1["value"] = "www.google.com"; service.process(request1); QVariantMap request2; request2["cmd"] = "set_config"; request2["key"] = "proxy"; request2["value"] = "192.168.100.1"; service.process(request2); QVariantMap request3; request3["cmd"] = "get_setting"; request3["key"] = "proxy"; QVariantMap reply3 = service.process(request3); os << "\nproxy: " << reply3["value"].toString() << endl; QVariantMap request4; request4["cmd"] = "get_json"; QVariantMap reply4 = service.process(request4); os << "\njson:\n" << reply4["json"].toByteArray() << endl; return 0; }
對比第一個例子,這里將request1改為set_setiing,request3改為get_setting,運行結果仍然是一樣的:
proxy: 192.168.100.1 json: { "proxy": "192.168.100.1", "search-engine": "www.google.com" }
以上是利用Qt實現C++反射的兩個例子,兩個例子都實現了通過函數名動態調用處理函數。不難看出,為了動態調用處理函數,我們需要建立函數名和函數對應關系,而利用Qt的特性則簡化了這一過程,使我們無需編寫復雜的代碼。
