7

几分钟前我正在回答一个问题,它向我提出了另一个问题:

在我的一个项目中,我做了一些网络消息解析。消息的形式为:

[1 byte message type][2 bytes payload length][x bytes payload]

有效载荷的格式和内容由消息类型决定。我有一个基于公共类的类层次结构Message

为了实例化我的消息,我有一个静态解析方法,它Message*根据消息类型返回 a 字节。就像是:

Message* parse(const char* frame)
{
  // This is sample code, in real life I obviously check that the buffer
  // is not NULL, and the size, and so on.

  switch(frame[0])
  {
    case 0x01:
      return new FooMessage();
    case 0x02:
      return new BarMessage();
  }

  // Throw an exception here because the mesage type is unknown.
}

我有时需要访问子类的方法。因为我的网络消息处理必须很快,所以我决定避免dynamic_cast<>并且我在基Message类中添加了一个方法来返回消息类型。根据这个返回值,我使用 astatic_cast<>来代替正确的子类型。

我这样做主要是因为有人告诉我这样做dynamic_cast<>很慢。但是,我不知道它到底做了什么以及它有多慢,因此,我的方法可能同样慢(或更慢)但要复杂得多。

大家觉得这个设计怎么样?这很常见吗?它真的比使用更快dynamic_cast<>吗?欢迎对一次使用时引擎盖下发生的事情进行任何详细解释dynamic_cast<>

- - 编辑 - -

既然有人问为什么:

基本上,当我收到一个帧时,我会做两件事:

  1. 我解析消息并构建Message框架内容是否有效的子类的相应实例。除了解析部分没有逻辑。
  2. 我收到 aMessage并根据 a switch(message->getType()),我static_cast<>选择正确的类型并执行任何必须对消息执行的操作。
4

9 回答 9

7

dynamic_cast 的实现当然会因编译器而异。

在 Visual C++ 中,vtable 指向一个结构,该结构包含有关结构的所有 RTTI。因此,dynamic_cast 涉及取消引用此指针,并根据请求的类型检查“实际”类型,如果它们不兼容则抛出异常(或返回 NULL)。它基本上等同于您描述的系统。不是特别慢。

你的设计听起来也有点不对劲——你有一个忘记对象真实类型的工厂方法,然后你马上想忘记那个信息。也许您应该将在忘记类型时所做的逻辑转移到工厂方法中,或者转移到基类本身的虚拟方法中。

于 2010-05-03T14:43:27.947 回答
4

“是否更快”的唯一正确答案是“试试看”。

于 2010-05-03T13:44:29.520 回答
3

这取决于您如何管理您的消息。当我必须switch根据类型选择消息时,最好的选择是使用static_cast,因为你知道函数解析器会给你创建正确的类型。

Message* gmsg parse(frame);

switch (gmsg->type) {
  case FooMessage_type:
    FooMessage* msg=static_cast<FooMessage*>(gmsg);
    // ...
    break;
  case BarMessage_type:
    BarMessage* msg=static_cast<BarMessage*>(gmsg);
    //...
    break;      
};

这里的使用dynamic_cast是过度保护。

为什么你需要所有的消息都继承自一个共同的消息?有哪些共同点?我将添加另一个根本不使用继承的设计

switch (frame::get_msg_type(aframe)) {
  case FooMessage_type:
    FooMessage msg=parse<FooMessage>(aframe);
    // work with msg
    break;
  case BarMessage_type:
    BarMessage msg=parse<BarMessage>(aframe);
    //...
    break;
};

其中 parse 将帧解析为 MSG,或者在解析失败时抛出异常。

我看到其他答案告诉您使用虚拟功能。我真的看不出这种面向消息的 OO 设计有什么优势。

于 2010-05-03T14:22:37.303 回答
3

当人们说 dynamic_cast 很慢时,这只是一个经验法则。dynamic_cast 或多或少地做了你正在做的事情。它很慢,因为它涉及几次内存访问。有点像人们说虚函数很慢。您正在快速执行某些操作(函数调用)并添加几个内存访问。这是一个显着的减速(因为一直到 ram 和返回可能需要几百个周期),但对于大多数人来说,只要不经常这样做就没有关系(经常值非常大) .

于 2010-05-03T14:46:12.930 回答
2

A)这听起来很像过早的优化。

B) 如果您的设计需要对 dynamic_cast<> 进行如此多的调用以至于您担心它,那么您肯定需要查看您的设计并找出它有什么问题。

C)就像前面的答案所说,回答它是否更快的唯一方法是使用分析器(或等效)并进行比较。

于 2010-05-03T13:47:36.367 回答
1

您关注的是速度,但正确性呢?

潜在的问题是你确定你不会犯错吗?特别是,您可能很想以这种方式包装强制转换方法:

template <class T>
T* convert(Message* message)
{
  if (message == 0) return 0;
  else return message->getType() == T::Type() ? static_cast<T*>(message) : 0;
}

为了将测试和转换嵌入到单个函数中,从而避免错误,例如:

switch(message->getType())
{
case Foo:
{
  //...
  // fallthrough
}
case Bar:
{
  BarMessage* bar = static_cast<BarMessage*>(message); // got here through `Foo`
}
}

或显而易见的:

if (message->getType() == Foo) static_cast<BarMessage*>(message); // oups

诚然,不多,但也没有太多的努力。

另一方面,您还可以通过应用运行时调度来查看您的技术。

  • virtual方法
  • Visitor

ETC...

于 2010-05-03T14:48:38.743 回答
0

您已经有一个抽象基类“消息”。使用它作为一个接口来隐藏 FooMessage 和 BarMessage 的实现细节。

我想,这就是您选择这种方法的原因,不是吗?

于 2010-05-03T14:23:12.403 回答
0

我知道这篇文章有点老了,但我和这个问题的作者有完全相同的问题。

我还需要MessageAbstract根据MessageHeader. 由于具体消息的长度和数据可能不同,因此不可能将所有内容都包含在MessageAbstract.

我也使用这种static_cast方法,因为我对 OOP 比对元编程更熟悉。

在当前项目中使用 C++11,我想在这个答案中概述我的解决方案。以下解决方案与 Vicente Botet Escriba 提供的解决方案非常相似,但使用的是Modern C++

#include <cstdint>
#include <memory>

namespace Example {

enum class MessageTypes : std::uint8_t {
  kFooMessage = 0x01,
  kBarMessage = 0x02
};

class MessageHeader {
 public:
  explicit MessageHeader(MessageTypes const kType) : kType_{kType} {
  }

  MessageTypes type() const noexcept {
    return this->kType_;
  }

 private:
  MessageTypes const kType_;
};

class MessageAbstract {
 public:
  explicit MessageAbstract(MessageHeader const kHeader) : kHeader_{kHeader} {
  }

  MessageHeader header() const noexcept {
    return this->kHeader_;
  }

 private:
  MessageHeader const kHeader_;
};

class FooMessage : public MessageAbstract {
 public:
  void specific_method_for_class_foo_message() const noexcept {
  }
  // ...
};

class BarMessage : public MessageAbstract {
 public:
  void specific_method_for_class_bar_message() const noexcept {
  }
  // ...
};

using MessagePointer = std::shared_ptr<MessageAbstract const>;

}  // namespace Example

using namespace Example;

int main() {
  MessagePointer message_ptr{/* Creation Method / Factory Method */};

  switch (message_ptr->header().type()) {
    case MessageTypes::kFooMessage: {
      std::shared_ptr<FooMessage const> foo_message{std::static_pointer_cast<FooMessage const>(message_ptr)};
      foo_message->specific_method_for_class_foo_message();
      // ...
      break;
    }
    case MessageTypes::kBarMessage: {
      std::shared_ptr<BarMessage const> bar_message{std::static_pointer_cast<BarMessage const>(message_ptr)};
      bar_message->specific_method_for_class_bar_message();
      // ...
      break;
    }
    default:
      // Throw exception.
      break;
  }

  return 0;
}
于 2014-04-17T12:34:52.303 回答
-1

我没有看到任何与此相关的答案,但是您不能通过网络发送 C++ 对象并期望它们完好无损地到达。虚拟表是根据发送计算机中的内存状态设置的,接收计算机很可能不会在同一个地方有东西。这通常也会使 RTTI 失败(这是 dynamic_cast 使用的),因为 RTTI 通常与 vtable 一起实现。

于 2010-05-03T21:43:39.890 回答