代码之家  ›  专栏  ›  技术社区  ›  ereOn

我的方法是避免动态铸造比动态铸造本身更快吗?

  •  7
  • ereOn  · 技术社区  · 14 年前

    我在回答 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<> 我给基地增加了一个方法 消息 返回消息类型的类。根据这个返回值,我使用 static_cast<> 改为正确的子类型。

    我这么做主要是因为有人告诉我 动态铸造 很慢。不过,我不太清楚 它真正的作用是什么 因此,我的方法可能同样慢(或慢),但要复杂得多。

    你们觉得这个设计怎么样?常见吗?它真的比用的快吗 动态铸造 ?任何详细的解释在使用时引擎盖下会发生什么 动态铸造 欢迎光临!

    ---编辑---

    因为有人问为什么:

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

    1. 我解析消息并构建子类的相应实例 消息 如果框架的内容有效。有 逻辑分析部分除外。
    2. 我收到一个 消息 而且取决于 switch(message->getType()) 静态铸造 选择正确的类型,并对消息做任何必须做的事情。
    9 回复  |  直到 10 年前
        1
  •  7
  •   Terry Mahaffey    14 年前

    当然,动态类型转换的实现会因编译器而异。

    在VisualC++中,VToT指向一个包含所有关于结构的RTTI的结构。因此,动态类型转换涉及取消对此指针的引用,并根据请求的类型检查“实际”类型,如果不兼容则抛出异常(或返回空值)。它基本上相当于您描述的系统。不是特别慢。

    你的设计听起来也有点离谱——你有一个工厂方法,它会忘记一个对象的真实类型,然后你会立即想要取消忘记这个信息。也许您应该在将一个类型取消强制转换为工厂方法或基类本身的虚拟方法时移动您所执行的逻辑。

        2
  •  4
  •   Joris Timmermans    14 年前

    唯一正确的答案是“试一下”。

        3
  •  3
  •   stonemetal    14 年前

    当人们说动态投射很慢时,这只是一个经验法则。动态角色扮演或多或少会做你正在做的事情。它很慢,因为它需要几个内存访问。有点像人们说虚拟功能很慢。您正在进行一些快速的操作(函数调用),并添加一些内存访问。这是一个明显的减速(因为一路到随机存取存储器和返回可能需要几百个周期),但对大多数人来说,只要不经常做(经常值很大),它就不重要了。

        4
  •  3
  •   Vicente Botet Escriba    14 年前

    这取决于您如何管理消息。当我有一个 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;
    };
    

    解析将帧解析为msg,或者在解析失败时引发异常。

    我看到了另一个告诉您使用虚拟函数的答案。我真的不认为这种面向对象的消息设计有任何优势。

        5
  •  2
  •   jwismar    14 年前

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

    b)如果您的设计需要对dynamic cast进行如此多的调用,以至于您担心它,那么您肯定需要查看您的设计并找出它的问题所在。

    c)正如前面的答案所说,回答是否更快的唯一方法是使用分析器(或等效工具)并进行比较。

        6
  •  1
  •   Matthieu M.    14 年前

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

    潜在的问题是你确定你不会犯错误吗?尤其是,您可能会尝试以这样的方式包装铸造方法:

    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

    等。。。

        7
  •  0
  •   user331471    14 年前

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

    我想,这就是为什么你选择了这种方法,或者不是吗?

        8
  •  0
  •   Florian Wolters    10 年前

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

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

    我也使用 static_cast 方法,因为我更熟悉OOP而不是元编程。

    在当前项目中使用C++ 11,我想在这个答案中勾勒出我的解决方案。以下解决方案与Vicente Boet Escriba提供的解决方案非常相似,但使用 现代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;
    }
    
        9
  •  -1
  •   dash-tom-bang    14 年前

    我没有看到任何关于这个问题的答案,但是你不能通过网络发送C++对象并期望它们完整地到达。虚拟表是根据发送计算机中的内存状态设置的,很可能接收计算机在同一位置上不会有东西。这通常也会使RTTI失败(这是动态资源分配使用的),因为RTTI通常与vtable一起实现。