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的特性則簡化了這一過程,使我們無需編寫復雜的代碼。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM