繁体   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