如何在C++中實現反射機制,應該算是C++開發中常常遇到的問題之一。C++程序沒有完整的元數據,也就沒法實現原生的反射機制。從性能的角度講,這樣的設計不難理解,畢竟在運行時儲存這些元數據須要額外的開銷。不爲你不使用的東西付出代價,這是C++的哲學,因此當咱們須要反射機制時,咱們得本身來實現它。所幸現在各類C++的反射實現已經至關成熟,好比boost::reflect,以及本文所使用的Qt。json
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。除此以外,普通成員函數是沒法被元數據對象調用的。this
再看service.cpp:google
#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:spa
#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的特性則簡化了這一過程,使咱們無需編寫複雜的代碼。