如何在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 ;
}

程序本身并没有直接调用处理函数,而是根据输入的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 ;
}

对比第一个例子,这里将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的特性则简化了这一过程,使我们无需编写复杂的代码。

Qt and C++ Reflection,利用Qt简化C++的反射实现的更多相关文章

  1. 【翻译】利用Qt设计师窗体在运行时创建用户界面(Creating a user interface from a Qt Designer form at run-time)

    利用Qt设计师窗体在运行时创建用户界面 我们利用Calculator窗体例子中创建的窗体(Form)来展示当一个应用(application)已经生成后,是可以在其运行时产生与例子中相同的用户界面. ...

  2. 利用Qt Assistant 定制帮助文档

    为了将Qt Assistant定制为自己应用程序的帮助文档浏览器.需要完成以下几步: 一.导入HTML格式的帮助文档 (1)首先,针对自己的应用程序创建HTML格式的帮助文档,请参见<Doxyg ...

  3. 利用Qt调用计算器

    之前有了第一个项目那么很快就会有第二个 这次 我们来调用 一些系统函数. 就不从头写了. 直接写比较重要的地方,如果又不太懂的地方欢迎小纸条或者参见利用 QT制作一个 helloworld http: ...

  4. 发布利用 Qt Assistant来做帮助系统的程序遇到的问题

    最近,一直在做反演初始模型可视化建模的软件 model Constraint,最后的步骤就是利用 Qt Assistant为自己的程序制作帮助系统. 1.<Qt Creator快速入门>和 ...

  5. 利用Qt开发跨平台APP

    本文将手把手教你如何在Windows环境下,使用Qt编译出安卓应用程序. Qt是一个优秀的跨平台开发工具.我们利用Qt可以很方便地将一次编写的应用,多次编译到不同平台上,如Windows.Linux. ...

  6. 利用Qt开发跨平台APP(二)(iOS,使用Qt5.9,很详细,有截图)

    本文将手把手教你如何使用Qt编译出iOS应用程序. Qt是一个优秀的跨平台开发工具.我们利用Qt可以很方便地将一次编写的应用,多次编译到不同平台上,如Windows.Linux.MAC.Android ...

  7. QMetaEnum利用Qt元数据实现枚举(enum)类型值及字符串转换

    版权声明:若无来源注明,Techie亮博客文章均为原创. 转载请以链接形式标明本文标题和地址: 本文标题:QMetaEnum利用Qt元数据实现枚举(enum)类型值及字符串转换     本文地址:ht ...

  8. qt利用QT designer构建第一个界面helloworld工程

    qt利用QT designer构建第一个界面helloworld工程原创ZJE_ANDY 发布于2017-04-07 20:25:28 阅读数 6613 收藏展开第一步:点击New Project 第 ...

  9. Qt 学习之路 :Qt 模块简介

    Qt 5 与 Qt 4 最大的一个区别之一是底层架构有了修改.Qt 5 引入了模块化的概念,将众多功能细分到几个模块之中.Qt 4 也有模块的概念,但是是一种很粗的划分,而 Qt 5 则更加细化.本节 ...

随机推荐

  1. Android 6.0 新特性

    首先谈一谈Android 6.0的一些新特性 锁屏下语音搜索 指纹识别 更完整的应用权限管理 Doze电量管理 Now onTap App link 在开发过程中与我们关系最密切的就是"更完 ...

  2. CSS之过渡简单应用—日落西山

    代码: <!DOCTYPE html><html><head> <title>日落西山</title> <meta charset=& ...

  3. 弹性盒子之wap端布局

    发现移动端用弹性盒子布局起来更加的方便,box-flex:val;特别是图文展示的时候,如下面的 <!DOCTYPE html> <html> <head> < ...

  4. 《Eclipse中的一些快捷键》

    1,window--首选项--Java--templates 自定义模板. 2,快捷键. 2.1 alt+/ 内容辅助. 2.2 ctrl+1 : 对小红×的提供解决法案.大红×(代码错误)必须修改代 ...

  5. Spring Security 从配置入门 学习讲解。刽子手------------securityConfig.xml

    不知道我的web.xml 大家都理解了没.  废话确实有点多,可能很多知识点,大家都知道,可是我学的时候,压根什么都不懂啊.... 这篇我们要讲刽子手  securityConfig. 为什么要说他是 ...

  6. Enterprise Architect 学习 之 用例图

    用例模型 用例模型用来记录系统的需求,它提供系统与用户及其他参与者的一种通信手段. 执行者 用例图显示了系统和系统外实体之间的交互.这些实体被引用为执行者.执行者代表角色,可以包括:用户,外部硬件和其 ...

  7. 为什么匿名内部类参数必须为final类型

    1)  从程序设计语言的理论上:局部内部类(即:定义在方法中的内部类),由于本身就是在方法内部(可出现在形式参数定义处或者方法体处),因而访问方法中的局部变量(形式参数或局部变量)是天经地义的.是很自 ...

  8. PyCharm 3.4.1注册码

    PyCharm 3.4下载地址:http://www.jetbrains.com/pycharm/download/index.html PyCharm 3.0 注册码 PyCharm3 序列号 Li ...

  9. htop基本使用

    一.什么是htop? top是所有类unix系统的必备工具,能直观方便的查看到系统负载.内存及进程等信息. 而htop具有top工具的全部功能且还新增了一些额外的功能和使用体验改进.与top相比,其具 ...

  10. hdu1004

    Problem Description Contest time again! How excited it is to see balloons floating around. But to te ...