避免使用派生类的dynamic_cast(Cast Derived类)

时间:2013-07-09 11:42:48

标签: c++ qt derived-class derived dynamic-cast

我是C ++的新手并且达到了一个目的,我在这里产生了一些开销。我有一个QTcpSocket并从中读取消息并创建对象,例如MessageJoin,MessagePart,MessageUserData等。我将这些对象发送到我的客户端并显示它们(+做一些UI更新)。

现在我的问题来了。我测试了一些设计技巧,但所有这些都不是那么好:

  • 将信号/插槽连接中的消息对象的每个参数传递给客户端 - 开销很小但不是很好看
  • 为每个Message-Type创建一个方法(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
    }
}

3 个答案:

答案 0 :(得分:2)

更好的设计可能是在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来执行此操作。

这里的主要想法是尽量减少类之间的耦合和依赖关系。它的耦合越少,修改一个或另一个就越容易,或者添加新的消息类,甚至完全重写一个或另一个所涉及的类而不影响其他类。这就是我们将代码分为接口,实现和数据隐藏的原因。

答案 1 :(得分:2)

这看起来非常类似于the expression problem和AFAIK,如果要添加新消息和处理它们的新方法,则无法避免强制转换。然而,对于必要的运行时间来说,使用更令人愉悦的包装并不难。只需使用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或增强信号/插槽,这样您就可以为单个消息提供多个处理程序,但核心思想是相同的。

答案 2 :(得分:1)

visitor pattern可能是一个不错的选择,即

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