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

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

时间: 2024-10-06 00:30:10

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

[Qt 5.6.2] 利用Qt实现一个难度可变的2048小游戏

利用Qt实现一个难度随时可调的2048小游戏 一.游戏简介 1.规则创新 胜利条件:达成2048 难度变化:玩家可以通过调整难度条来控制随机池(2.4.8.16)中各个数出现的概率,同时也会改变分数的加成比例 移动触发:每次移动后会从随机池中按照概率随机选取一个数,将其随机放置在一个空余位置上 分数计算:总分=基础分+加成分,基础分+=合并的数值,加成分+=随机生成的数值*加成比例 2.游戏效果 二.设计思路 先将该项目分为游戏入口.主窗口设计与游戏逻辑三个主要模块,再在这三个模块的基础上继续细

利用Qt调用计算器

之前有了第一个项目那么很快就会有第二个 这次 我们来调用 一些系统函数. 就不从头写了. 直接写比较重要的地方,如果又不太懂的地方欢迎小纸条或者参见利用 QT制作一个 helloworld http://www.cnblogs.com/letben/p/5205060.html 1.双击widget.ui到设计界面. 2.拖一个PushButton到中间灰色区域内. 3.右键PushButton在下拉菜单中转到槽. 4.在跳转之后,键入:system.("calc"); 如图: 5.构

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

最近,一直在做反演初始模型可视化建模的软件 model Constraint,最后的步骤就是利用 Qt Assistant为自己的程序制作帮助系统. 1.<Qt Creator快速入门>和网上大部分的资料在介绍利用Qt Assistant为自己的程序制作帮助系统时,制作的帮助系统里都没有图片,都是全文字的.而我写的用户手册里面含有一些操作图示,所以需要图片.并且用户手册是用 WPS写的,一些图片就是浮与文字上方.之后转成 HTML文件后,会生成一个 html文件和同名文件夹,里面存放着 png

利用Qt Global Object来获取一些关于应用的信息

在Qt中,我们可以利用Qt全局变量来获取一些对我们应用有用的信息.在下面的应用中,我们可以获取如下的信息:    在上面,我们可以看到应用的状态,运行的输入参数,应用的名称及操作系统等. 我们的应用设计非常简单: import QtQuick 2.0 import Ubuntu.Components 1.1 import Ubuntu.Components.ListItems 1.0 as ListItems /*! \brief MainView with a Label and Button

利用QT中Qpainter画点,直线,弧线等简单图形

MyImgTest.h: #ifndef MYIMGTEST_H #define MYIMGTEST_H #include <QWidget> class MyImgTest : public QWidget { //Q_OBJECT public: MyImgTest(QWidget* parent = 0); ~MyImgTest(); void paintEvent(QPaintEvent *); }; #endif MyImgTest.cpp: #include "MyImg

嵌入式linux QT开发(一)——QT简介

嵌入式linux QT开发(一)--QT简介 一.QT简介 1.QT简介 QT是一个跨平台的C++图形用户界面库,由挪威TrollTech公司出品,目前包括Qt Creator, QtEmbedded,Qt Designer快速开发工具,Qt Linguist国际化工具等部分,Qt支持所有Linux/Unix系统,还支持Windows平台. 2.QT优点 Qt是一个跨平台的C++图形用户界面应用程序框架,提供给应用程序开发者建立艺术级的图形用户界面所需的所用功能.Qt很容易扩展,并且允许真正地组

QT开发(十三)——QT信号与槽机制

QT开发(十三)--QT信号与槽机制 一.QT消息模型 QT封装了具体操作系统的消息机制,遵循经典的GUI消息驱动事件模型. QT定义了与操作系统消息相关的自己的概念,即信号与槽. 信号signal是由操作系统产生的消息. 槽slot是程序中的消息处理函数. connect将系统消息绑定到消息处理函数. 信号到槽的连接必须发生在两个QT对象间. bool QObject::connect ( const QObject * sender, //发生对象 const char * signal,

QT笔记(1)--QT编程环境搭建

一.QT简介 Qt  是一个1991年由奇趣科技开发的跨平台C++图形用户界面应用程序开发框架.它既可以开发GUI程序,也可用于开发非GUI程序,比如控制台工具和服务器.Qt是面向对象的框架,使用特殊的代码生成扩展(称为元对象编译器(Meta Object Compiler, moc))以及一些宏,易于扩展,允许组件编程.2008年,奇趣科技被诺基亚公司收购,QT也因此成为诺基亚旗下的编程语言工具.2012年,Qt被Digia收购.2014年4月,跨平台集成开发环境Qt Creator 3.1.

【大话QT之十四】QT实现多语言切换

功能需求: 网盘客户端要能够实现多国语言的切换,第一版要支持中.英文的切换.在实现过程中感觉QT对多国语言的支持还是很不错的,制作多语言包很方便,切换的逻辑也很简单.下面就来看一下QT中如何制作多语言包. 实现方法: 为了支持国际化最关键的地方是制作多国语言包,然后再实现动态切换.QT里面既可以采用命令行也可以采用Qt Creator的界面操作来生成,这里我们利用Qt Creator来生成多国语言包.基本流程是,先生成ts文件,然后生成qm文件,最后通过QTranslator类来加载qm文件,实