简体   繁体   English

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

[英]Is my method for avoiding dynamic_cast<> faster than dynamic_cast<> itself?

I was answering a question a few minutes ago and it raised to me another one:几分钟前我正在回答一个问题,它向我提出了另一个问题:

In one of my projects, I do some network message parsing.在我的一个项目中,我做了一些网络消息解析。 The messages are in the form of:消息的形式如下:

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

The format and content of the payload are determined by the message type.有效载荷的格式和内容由消息类型决定。 I have a class hierarchy, based on a common class Message .我有一个基于公共类Message的类层次结构。

To instantiate my messages, i have a static parsing method which gives back a Message* depending on the message type byte .为了实例化我的消息,我有一个静态解析方法,它根据消息类型 byte返回Message* Something like:就像是:

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

I sometimes need to access the methods of the subclasses.我有时需要访问子类的方法。 Since my network message handling must be fast, I decived to avoid dynamic_cast<> and I added a method to the base Message class that gives back the message type.由于我的网络消息处理必须很快,我决定避免使用dynamic_cast<>并且我在基本Message类中添加了一个方法来返回消息类型。 Depending on this return value, I use a static_cast<> to the right child type instead.根据这个返回值,我改为使用static_cast<>到右子类型。

I did this mainly because I was told once that dynamic_cast<> was slow.我这样做主要是因为有人告诉我dynamic_cast<>很慢。 However, I don't know exactly what it really does and how slow it is, thus, my method might be as just as slow (or slower) but far more complicated.但是,我不知道到底做了什么以及它有多慢,因此,我的方法可能同样慢(或更慢)但要复杂得多。

What do you guys think of this design ?大家觉得这个设计怎么样? Is it common ?常见吗? Is it really faster than using dynamic_cast<> ?它真的比使用dynamic_cast<>快吗? Any detailed explanation of what happen under the hood when one use dynamic_cast<> is welcome !欢迎对使用dynamic_cast<>时发生的情况进行任何详细解释!

--- EDIT --- - - 编辑 - -

Since some people asked why:既然有人问为什么:

Basically, when I receive a frame, I do two things:基本上,当我收到一个帧时,我会做两件事:

  1. I parse the message and build a corresponding instance of a subclass of Message if the content of the frame is valid.如果框架的内容有效,我会解析消息并构建Message子类的相应实例。 There is no logic except for the parsing part.除了解析部分没有逻辑。
  2. I receive a Message and depending on a switch(message->getType()) , I static_cast<> to the right type and do whatever has to be done with the message.我收到一条Message并根据switch(message->getType()) ,我static_cast<>到正确的类型,并做任何必须对消息做的事情。

Implementations of dynamic_cast will of course vary by compiler. dynamic_cast 的实现当然会因编译器而异。

In Visual C++, the vtable points to a structure which contains all of the RTTI about a structure.在 Visual C++ 中,vtable 指向一个结构,其中包含有关结构的所有 RTTI。 A dynamic_cast therefore involves dereferencing this pointer, and checking the "actual" type against the requested type, and throwing an exception (or returning NULL) if they are not compatible.因此,dynamic_cast 涉及取消引用该指针,并根据请求的类型检查“实际”类型,如果它们不兼容,则抛出异常(或返回 NULL)。 It is basically equivalent to the system you describe.基本上相当于你描述的系统。 It's not particularity slow.它不是特别慢。

Your design also sounds a bit off - you have a factory method which forgets the true type of an object, then you immediately want to un-forget that information.你的设计听起来也有点不对劲——你有一个工厂方法,它忘记了一个对象的真实类型,然后你马上想忘记那个信息。 Perhaps you should move that logic you do when unforgetting a type into the factory method, or into virtual methods on the base class itself.也许您应该将忘记类型时执行的逻辑移动到工厂方法中,或者移动到基类本身的虚拟方法中。

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

This depends on how you manage your messages.这取决于您如何管理您的消息。 When I have a switch to select the message based on the type the best option is to use static_cast , as you know that the function parser will give you the create the correct type.当我有一个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;      
};

The use of dynamic_cast here is overprotecting.此处使用dynamic_cast是过度保护。

Why do you need that all the messages inherits from a common one?为什么你需要所有的消息都继承自一个共同的消息? What are the commonalities?有哪些共同点? I will add another design which doesn't use inheritance at all我将添加另一个完全不使用继承的设计

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

where parse parses a frame as a MSG, or throw an exception when the parse fails.其中 parse 将帧解析为 MSG,或者在解析失败时抛出异常。

I see other answer that are telling you to use virtual functions.我看到其他答案告诉您使用虚函数。 I really don't see any advantage to this OO design for messages.我真的看不出这种面向消息的 OO 设计有什么优势。

When people say dynamic_cast is slow it is just a rule of thumb.当人们说 dynamic_cast 很慢时,这只是一个经验法则。 dynamic_cast more or less does what you are doing. dynamic_cast 或多或少会做你正在做的事情。 It is slow because it involves a couple of memory accesses.它很慢,因为它涉及几个内存访问。 Kind of like when people say virtual functions are slow.有点像人们说虚函数很慢。 You are taking something fast(a function call) and adding a couple of memory accesses to it.您正在快速执行某些操作(函数调用)并为其添加一些内存访问。 It is a significant slowdown(since all the way to ram and back can take a few hundred cycles), but for most people it just doesn't matter as long as it isn't done often(with a very large value of often).这是一个显着的减速(因为一直到 ram 和 back 可能需要几百个周期),但对于大多数人来说,只要不经常这样做就无所谓(经常有非常大的值) .

A) This sounds very much like premature optimization. A) 这听起来很像过早的优化。

B) If your design requires so many calls to dynamic_cast<> that you're worried about it, then you definitely need to take a look at your design and figure out what's wrong with it. B) 如果您的设计需要多次调用 dynamic_cast<> 以至于您担心它,那么您肯定需要查看您的设计并找出它的问题所在。

C) Like the previous answer said, the only way to answer whether it's faster is to use a profiler (or equivalent) and do a comparison. C)就像之前的答案所说,回答它是否更快的唯一方法是使用分析器(或等效工具)并进行比较。

You are focusing on the speed, but what of the correctness ?你关注的是速度,但正确性呢?

The underlying question is are you sure you won't make a mistake ?潜在的问题是你确定你不会犯错吗? In particular, you might be tempted to wrap the casting method in such a way:特别是,您可能会想以这种方式包装铸造方法:

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

In order to embed the test and cast in a single function, thus avoiding error such as:为了在单个函数中嵌入测试和转换,从而避免错误,例如:

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

or the obvious:或者显而易见的:

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

Not much admittedly, but it's not much effort either.不可否认,这并不多,但也不是很努力。

On the other hand you can also review your technic by applying runtime dispatch.另一方面,您还可以通过应用运行时调度来检查您的技术。

  • virtual methods virtual方法
  • Visitor

etc...等等...

I know that this post is a bit old, but I had exactly the same problem as the author of this question.我知道这篇文章有点旧,但我和这个问题的作者遇到了完全相同的问题。

I also need to downcast an abstract base class ( MessageAbstract ) to one or more concrete message classes in dependency of a type field in a MessageHeader .我还需要根据MessageHeader中的类型字段将抽象基类 ( MessageAbstract ) 向下转换为一个或多个具体消息类。 Since the concrete messages can differ in their length and their data, there is no possibility to include everything in MessageAbstract .由于具体消息的长度和数据可能不同,因此不可能在MessageAbstract包含所有内容。

I also use the static_cast approach, since I am more familiar with OOP than with metaprogramming.我也使用static_cast方法,因为我对 OOP 比元编程更熟悉。

Using C++11 in the current project, I wanted to outline my solution in this answer.在当前项目中使用 C++11,我想在这个答案中概述我的解决方案。 The following solution is very similar to the one provided by Vicente Botet Escriba, but uses Modern C++ .以下解决方案与 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;
}

You already have an abstract base class "Message".您已经有了一个抽象基类“Message”。 Use it as an interface to hide the implementation details of FooMessage and BarMessage.将其作为一个接口来隐藏FooMessage和BarMessage的实现细节。

I guess, that is why you have chosen that approach, or isn't it?我想,这就是你选择这种方法的原因,不是吗?

I don't see any answers touching on this, but you can't send C++ objects over the network and expect them to arrive intact.我没有看到任何与此相关的答案,但是您不能通过网络发送 C++ 对象并期望它们完好无损地到达。 The virtual table is set up based on the state of memory in the sending computer, it is quite likely that the receiving computer will not have things in the same place.虚拟表是根据发送计算机的内存状态建立的,很可能接收计算机不会在同一个地方有东西。 This will also typically make RTTI fail (which is what dynamic_cast uses) since RTTI is often implemented along with the vtable.这通常也会使 RTTI 失败(这是 dynamic_cast 使用的),因为 RTTI 通常与 vtable 一起实现。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM