Qt and C++ Reflection,利用Qt简化C++的反射实现

时间:2021-12-03 20:41:18

如何在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&period;9,很详细,有截图)

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

  7. QMetaEnum利用Qt元数据实现枚举&lpar;enum&rpar;类型值及字符串转换

    版权声明:若无来源注明,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. 3&period;Java Script 类型

     true: null==undefinedfalse: null===undefined   

  2. &lbrack;Usaco2006 Nov&rsqb;Corn Fields牧场的安排 壮压DP

    看到第一眼就发觉是壮压DP 然后就三进制枚举子集吧. 这题真是壮压入门好题... 对于dp[i][j] 表示第i行,j状态下前i行的分配方案数. 那么dp[i][j]肯定是从i-1行转过来的 那么由于 ...

  3. 关于fixed-point

    今天又出现了shader的问题,编译到真机效果就没了,后来仔细还是因为浮点数精度的问题,后来仔细查找了些资料,才发现自己太粗心,没有看清楚 fixed-point 数据类型就乱用,这是个范围在 [-1 ...

  4. &lbrack;C入门 - 游戏编程系列&rsqb; 贪吃蛇篇&lpar;六&rpar; - 蛇实现

    这一篇是关于设置蛇的属性的,接上一篇(五). 设置蛇的速度,很简单,只要不是负数就行了. void SNK_SetSnakeSpeed(Snake *snake, int speed) { ) sna ...

  5. Android自己定义控件系列一:Android怎样实现老版优酷client三级环形菜单

    转载请附上本文链接:http://blog.csdn.net/cyp331203/article/details/40423727 先来看看效果: 一眼看上去好像还挺炫的,感觉比較复杂...实际上并不 ...

  6. 【C&num;多线程编程实战笔记】一、 线程基础

    创建线程 Thread :所执行的方法不能有参数. class Program { static void Main(string[] args) { Console.WriteLine(" ...

  7. C&num; 多线程学习系列三之CLR线程池系列之ThreadPool

    一.CLR线程池 1.进程和CLR的关系一个进程可以只包含一个CLR,也可以包含多个CLR2.CLR和AppDomain的关系一个CLR可以包含多个AppDomain3.CLR和线程池的关系一个CLR ...

  8. JFreeChart DateAxis用法

    http://blog.csdn.net/xiaozhendong123/article/details/50131513

  9. Python2中编码错误---&&num;233&semi;‡&&num;231&semi;&&num;187&semi;„&&num;228&semi;&&num;186&semi;&&num;186&semi;&&num;232&semi;&&num;161&semi;&&num;168&semi;&&num;231&semi;š&&num;174&semi;&&num;231&semi;”Ÿ&&num;233&semi;•&&num;191&semi;&&num;229&semi;›&&num;160&semi;&&num;229&semi;&&num;173&semi;&&num;229&semi;‡&&num;232&semi;ƒ&&num;182&semi;&lpar;&&num;230&semi;˜“&&num;229&semi;&&num;173&semi;š格式转化为UTF-8

    在python2的使用中,总会遇到各种各样的编码问题,这也是使用Python2最头疼的一件事情,幸好python3解决了编码的问题. 下面我在爬虫时遇到的类似重组人表皮生长 ...

  10. A Pangram

    Codeforces Round #295 div2 的A题,题意是判读一个字符串是不是全字母句,也就是这个字符串是否包含了26个字母,无论大小写. Sample test(s) input 12 t ...