Qt and C++ Reflection,利用Qt簡化C++的反射實現

如何在C++中實現反射機制,應該算是C++開發中常常遇到的問題之一。C++程序沒有完整的元數據,也就沒法實現原生的反射機制。從性能的角度講,這樣的設計不難理解,畢竟在運行時儲存這些元數據須要額外的開銷。不爲你不使用的東西付出代價,這是C++的哲學,因此當咱們須要反射機制時,咱們得本身來實現它。所幸現在各類C++的反射實現已經至關成熟,好比boost::reflect,以及本文所使用的Qt。json

 

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。除此以外,普通成員函數是沒法被元數據對象調用的。this

 

再看service.cpp:google

#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:spa

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

相關文章
相關標籤/搜索