简体   繁体   English

避免对派生类(Cast Derived类)进行dynamic_cast

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

I am new to C++ and came to a point, where I generate an overhead with classes. 我是C ++的新手,并且到了某种程度,我在类上产生了开销。 I have a QTcpSocket and read messages from it and create objects, for example MessageJoin, MessagePart, MessageUserData etc. I send these objects to my client and display them (+ do some UI updating). 我有一个QTcpSocket并从中读取消息并创建对象,例如MessageJoin,MessagePart,MessageUserData等。我将这些对象发送到我的客户端并显示它们(+做一些UI更新)。

Now here comes my problem. 现在是我的问题了。 I tested a few design techniques but all of them are not that nice: 我测试了一些设计技术,但它们都不是很好:

  • Pass each parameter of a message object in a signal/slot connection to the client - small overhead but not that good-looking 将信号对象/插槽连接中的消息对象的每个参数传递给客户端-开销很小但外观不太好
  • Create a method for each Message-Type (messageJoinReceived, messageNoticeReceived etc.) 为每个消息类型创建一个方法(messageJoinReceived,messageNoticeReceived等)
  • Create one method and use dynamic_cast to cast für each class and test it 创建一个方法并使用dynamic_cast浇铸每个类并对其进行测试

For a better understanding, I added my dynamic_cast version. 为了更好地理解,我添加了dynamic_cast版本。 As a said, the code looks ugly and unusable. 可以这么说,该代码看起来难看且无法使用。 My questions are : 我的问题是

  • Is there a better way to do it with (a) dynamic_cast 有没有更好的办法做到这一点(a)dynamic_cast
  • Is there another way (For example a design pattern) to solve such a problem ? 是否有另一种方法(例如设计模式)来解决此问题? maybe add a method in the classes and return the type or something like this 也许在类中添加一个方法并返回类型或类似的东西
  • I read about the visitor pattern. 我了解了访客模式。 This pattern is just for dynamic object types in Getter/Setter methods ? 这种模式仅适用于Getter / Setter方法中的动态对象类型吗?

A few side notes 一些注意事项

  • I can use RTTI 我可以使用RTTI
  • Speed isn't a big deal. 速度没什么大不了的。 Clean and understandable code is more important 干净易懂的代码更重要
  • I use Qt and have the possiblity to use qobject_cast and signal/slots 我使用Qt并有可能使用qobject_cast和signal / slots

Here is my code ( Pastebin-Link ): 这是我的代码( 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
    }
}

A better design might be to have an abstract virtual function in the Message class, called process or onReceive or similar, the sub-classes implements this function. 更好的设计可能是在Message类中有一个抽象的虚函数,称为processonReceive或类似函数,子类实现此函数。 Then in Client::messageReceived just call this function: 然后在Client::messageReceived只需调用此函数:

message->onReceive(...);

No need to for the dynamic_cast . 无需使用dynamic_cast

I would also recommend you to look into smart pointers, such as std::unique_ptr . 我还建议您研究智能指针,例如std::unique_ptr


If you have private data in the Client class that is needed for the message processing functions, then there are many methods of solving that: 如果您在Client类中具有消息处理功能所需的私有数据,那么可以采用多种方法来解决此问题:

  • The simplest is to use a plain "getter" function in the client: 最简单的方法是在客户端中使用普通的“ getter”函数:

     class Client { public: const QList<QString>& getList() const { return listContainingUiRelatedStuff; } // Add non-const version if you need to modify the list }; 
  • If you just want add items to the list in your example, then add a function for that: 如果您只想在示例中将项目添加到列表中,请为此添加一个函数:

     void addStringToList(const QString& str) { listContainingUiRelatedStuff.push_back(str); } 
  • Or the non-recommended variant, make Client a friend in all message classes. 还是非推荐的变体,请使Client在所有消息类别中成为friend

The second variant is what I recommend. 第二种是我的建议。 For example, if you have a list of all connected clients and want to send a message to all of them, then create a function sendAll that does it. 例如,如果您有所有已连接客户端的列表,并想向所有客户端发送消息,则创建一个功能sendAll来执行此操作。

The big idea here is to try and minimize the coupling and dependencies between your classes. 这里的主要想法是尝试最小化类之间的耦合和依赖性。 The less coupling there is, the easier it will be to modify one or the other, or add new message classes, or even completely rewrite one or the other of the involved classes without it affecting the other classes. 耦合越少,修改一个或另一个,或添加新的消息类,甚至完全重写其中一个或另一个涉及的类,而不影响其他类,就越容易。 This is why we split code into interface and implementation and data hiding. 这就是为什么我们将代码分为接口,实现和数据隐藏的原因。

This looks quite similar to the expression problem and AFAIK there is no way to avoid casts if you are going to add new messages and new ways to handle them. 这看起来与表达问题和AFAIK非常相似,如果要添加新消息和新方法来处理它们,则无法避免强制转换。 However it's not that hard to make more eye pleasing wrap for necessary run-time stuff. 但是,对必要的运行时内容进行更多令人愉悦的包装并不难。 Just create a map from message type to corresponding handler using typeid . 只需使用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";
    }
}

Then register handlers for specific message types: 然后注册特定消息类型的处理程序:


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
    });

And now you can handle messages: 现在您可以处理消息:


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

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

Surely you might want to make some improvements like wrapping handlers stuff into reusable class, using QT or boost signals/slots so you can have multiple handlers for a single message, but the core idea is the same. 当然,您可能需要进行一些改进,例如将处理程序内容包装到可重用的类中,使用QT或增强信号/插槽,以便您可以为一条消息使用多个处理程序,但是核心思想是相同的。

The visitor pattern could be a good fit ie 访客模式可能很合适,即

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