我的方法是避免dynamic_cast<>比dynamic_cast更快<>本身?

时间:2010-05-03 13:42:47

标签: c++ polymorphism dynamic-cast static-cast

几分钟前我正在回答question,然后再向我提出另一个问题:

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

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

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

要实例化我的消息,我有一个静态解析方法,根据消息类型字节返回Message*。类似的东西:

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类添加了一个方法,该类提供了消息类型。根据此返回值,我使用static_cast<>代替正确的子类型。

我之所以这样做,主要是因为我被告知dynamic_cast<>很慢。但是,我并不确切地知道它真正做了什么以及它有多慢,因此,我的方法可能同样慢(或慢)但更复杂。

你们对这个设计有什么看法?这很常见吗?它真的比使用dynamic_cast<>快吗?当一个人使用dynamic_cast<>时欢迎发生什么事情的详细解释!

---编辑---

因为有些人问为什么:

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

  1. 如果帧的内容有效,我解析消息并构建Message的子类的相应实例。除了解析部分之外,还有 no 逻辑。
  2. 我收到一个Message,并且根据switch(message->getType()),我static_cast<>为正确的类型,并对该消息执行任何操作。

9 个答案:

答案 0 :(得分:7)

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

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

您的设计听起来有些偏离 - 您有一个工厂方法会忘记对象的真实类型,然后您立即想要忘记这些信息。也许你应该在将类型遗忘到工厂方法时,或者在基类本身的虚拟方法中移动你所做的逻辑。

答案 1 :(得分:4)

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

答案 2 :(得分: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设计有任何优势。

答案 3 :(得分:3)

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

答案 4 :(得分:2)

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

B)如果你的设计需要这么多次调用dynamic_cast&lt;&gt;你担心它,那么你肯定需要看看你的设计并弄清楚它有什么问题。

C)与之前的回答一样,回答是否更快的唯一方法是使用分析器(或等效的)并进行比较。

答案 5 :(得分: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

等...

答案 6 :(得分:0)

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

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

答案 7 :(得分:0)

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

我还需要将一个抽象基类(MessageAbstract)向下转换为一个或多个具体的消息类,而不依赖于MessageHeader中的类型字段。由于具体消息的长度和数据可能不同,因此不可能在MessageAbstract中包含所有内容。

我也使用static_cast方法,因为我比Opr更熟悉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;
}

答案 8 :(得分:-1)

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