如何在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的特性则简化了这一过程,使咱们无需编写复杂的代码。