簡體   English   中英

避免對派生類(Cast Derived類)進行dynamic_cast

[英]Avoid dynamic_cast with derived classes (Cast Derived class)

我是C ++的新手,並且到了某種程度,我在類上產生了開銷。 我有一個QTcpSocket並從中讀取消息並創建對象,例如MessageJoin,MessagePart,MessageUserData等。我將這些對象發送到我的客戶端並顯示它們(+做一些UI更新)。

現在是我的問題了。 我測試了一些設計技術,但它們都不是很好:

  • 將信號對象/插槽連接中的消息對象的每個參數傳遞給客戶端-開銷很小但外觀不太好
  • 為每個消息類型創建一個方法(messageJoinReceived,messageNoticeReceived等)
  • 創建一個方法並使用dynamic_cast澆鑄每個類並對其進行測試

為了更好地理解,我添加了dynamic_cast版本。 可以這么說,該代碼看起來難看且無法使用。 我的問題是

  • 有沒有更好的辦法做到這一點(a)dynamic_cast
  • 是否有另一種方法(例如設計模式)來解決此問題? 也許在類中添加一個方法並返回類型或類似的東西
  • 我了解了訪客模式。 這種模式僅適用於Getter / Setter方法中的動態對象類型嗎?

一些注意事項

  • 我可以使用RTTI
  • 速度沒什么大不了的。 干凈易懂的代碼更重要
  • 我使用Qt並有可能使用qobject_cast和signal / slots

這是我的代碼( Pastebin-Link ):

// Default class - contains the complete message (untouched)
class Message
{
public:
    QString virtual getRawMessage() { return dataRawMessage; }
protected:
    QString dataRawMessage;
};

// Join class - cointains the name of the joined user and the channel
class MessageJoin : public Message
{
public:
    MessageJoin(const QString &rawmessage, const QString &channel, const QString &user)
    {
        dataRawMessage = rawmessage;
        dataChannel = channel;
        dataUser = user;
    }

    QString getChannel() { return dataChannel; }
    QString getUser(){ return dataUser; }

private:
    QString dataChannel;
    QString dataUser;
};

// Notice class - contains a notification message
class MessageNotice : public Message
{
public:
    MessageNotice(const QString &rawmessage, const QString &text)
    {
        dataRawMessage = rawmessage;
        dataText = text;
    }

    QString getText() { return dataText;}

private:
    QString dataText;
};

// Client code - print message and update UI
void Client::messageReceived(Message *message)
{
    if(message)
    {
        MessageJoin *messagejoin;
        MessagePart *messagepart;
        MessageNotice *messagenotice;
        if((messagejoin = dynamic_cast<MessageJoin *>(message)) != 0)
        {
            qDebug() << messagejoin->getUser() << " joined " << messagejoin->getChannel();
            // Update UI: Add user
        }
        else if((messagenotice = dynamic_cast<MessageNotice *>(message)) != 0)
        {
            qDebug() << messagenotice->getText();
            // Update UI: Display message
        }
        else
        {
            qDebug() << "Cannot cast message object";
        }
        delete message; // Message was allocated in the library and is not used anymore
    }
}

更好的設計可能是在Message類中有一個抽象的虛函數,稱為processonReceive或類似函數,子類實現此函數。 然后在Client::messageReceived只需調用此函數:

message->onReceive(...);

無需使用dynamic_cast

我還建議您研究智能指針,例如std::unique_ptr


如果您在Client類中具有消息處理功能所需的私有數據,那么可以采用多種方法來解決此問題:

  • 最簡單的方法是在客戶端中使用普通的“ getter”函數:

     class Client { public: const QList<QString>& getList() const { return listContainingUiRelatedStuff; } // Add non-const version if you need to modify the list }; 
  • 如果您只想在示例中將項目添加到列表中,請為此添加一個函數:

     void addStringToList(const QString& str) { listContainingUiRelatedStuff.push_back(str); } 
  • 還是非推薦的變體,請使Client在所有消息類別中成為friend

第二種是我的建議。 例如,如果您有所有已連接客戶端的列表,並想向所有客戶端發送消息,則創建一個功能sendAll來執行此操作。

這里的主要想法是嘗試最小化類之間的耦合和依賴性。 耦合越少,修改一個或另一個,或添加新的消息類,甚至完全重寫其中一個或另一個涉及的類,而不影響其他類,就越容易。 這就是為什么我們將代碼分為接口,實現和數據隱藏的原因。

這看起來與表達問題和AFAIK非常相似,如果要添加新消息和新方法來處理它們,則無法避免強制轉換。 但是,對必要的運行時內容進行更多令人愉悅的包裝並不難。 只需使用typeid創建從消息類型到相應處理程序的typeid


#include <functional>
#include <typeindex>
#include <typeinfo>
#include <unordered_map>

typedef std::function<void(Message *)> handler_t;

typedef std::unordered_map<
    std::type_index,
    handler_t> handlers_map_t;

template <class T, class HandlerType>
handler_t make_handler(HandlerType handler)
{
    return [=] (Message *message) { handler(static_cast<T *>(message)); };
}

template <class T, class HandlerType>
void register_handler(
    handlers_map_t &handlers_map,
    HandlerType handler)
{
    handlers_map[typeid(T)] = make_handler<T>(handler);
}

void handle(handlers_map_t const &handlers_map, Base *message)
{
    handlers_map_t::const_iterator i = handlers_map.find(typeid(*message));
    if (i != handlers_map.end())
    {
        (i->second)(message);
    }
    else
    {
        qDebug() << "Cannot handle message object";
    }
}

然后注冊特定消息類型的處理程序:


handlers_map_t handlers_map;

register_handler<MessageJoin>(
    handlers_map,
    [] (MessageJoin  *message)
    {
        qDebug() << message->getUser() << " joined " << message->getChannel();
        // Update UI: Add user
    });

register_handler<MessageNotice>(
    handlers_map,
    [] (MessageNotice *message)
    {
        qDebug() << message->getText();
        // Update UI: Display message
    });

現在您可以處理消息:


// simple test
Message* messages[] =
{
    new MessageJoin(...),
    new MessageNotice(...),
    new MessageNotice(...),
    new MessagePart(...),
};

for (auto m: messages)
{
    handle(handlers_map, m);
    delete m;
}

當然,您可能需要進行一些改進,例如將處理程序內容包裝到可重用的類中,使用QT或增強信號/插槽,以便您可以為一條消息使用多個處理程序,但是核心思想是相同的。

訪客模式可能很合適,即

class Message
{
public:
    QString virtual getRawMessage() { return dataRawMessage; }

    virtual void accept(Client& visitor) = 0;

protected:
    QString dataRawMessage;
};

// Join class - cointains the name of the joined user and the channel
class MessageJoin : public Message
{
public:
    MessageJoin(const QString &rawmessage, const QString &channel, const QString &user)
    {
        dataRawMessage = rawmessage;
        dataChannel = channel;
        dataUser = user;
    }

    QString getChannel() { return dataChannel; }
    QString getUser(){ return dataUser; }

    void accept(Client& visitor) override
    {
          visitor.visit(*this);
    }

private:
    QString dataChannel;
    QString dataUser;
};

// Notice class - contains a notification message
class MessageNotice : public Message
{
public:
    MessageNotice(const QString &rawmessage, const QString &text)
    {
        dataRawMessage = rawmessage;
        dataText = text;
    }

    QString getText() { return dataText;}

    void accept(Client& visitor) override
    {
          visitor.visit(*this);
    }

private:
    QString dataText;
};

void Client::visit(MessageJoin& msg)
{
    qDebug() << msg.getUser() << " joined " << msg.getChannel();
    // Update UI: Add user
}

void Client::visit(MessageNotice& msg)
{
    qDebug() << msg.getText();
    // Update UI: Display message
}

// Client code - print message and update UI
void Client::messageReceived(Message *message)
{
    if(message)
    {
        message->visit(this);
        delete message; // Message was allocated in the library and is not used anymore
    }
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM