zoukankan      html  css  js  c++  java
  • 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("
    Process finished.
    ");
        }
        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 << "
    proxy: " << reply3["value"].toString() << endl;
    
        QVariantMap request4;
        request4["cmd"] = "get_json";
        QVariantMap reply4 = service.process(request4);
        os << "
    json:
    " << 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 << "
    proxy: " << reply3["value"].toString() << endl;
    
        QVariantMap request4;
        request4["cmd"] = "get_json";
        QVariantMap reply4 = service.process(request4);
        os << "
    json:
    " << 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的特性则简化了这一过程,使我们无需编写复杂的代码。

  • 相关阅读:
    LINQ进阶(深入理解C#)11 查询表达式和LINQ to Objects
    (转)Dinktopdf在.net core项目里将Html转成PDF(支持liunx)
    asp.net core 实现 face recognition 使用 tensorflowjs(源代码)
    fastreport-使用JSON做为数据源报表
    分享我的第一个RPA练习
    关于性能优化技巧
    Sql 增删改查语句
    将结果集插入另一个表中
    Vue+elementUI 表格 增删改查 纯前端 最终版
    【JAVA】使用IntelliJ IDEA创建 maven的quickStart项目
  • 原文地址:https://www.cnblogs.com/randyyang/p/4290393.html
Copyright © 2011-2022 走看看