【问题标题】:dynamic_cast vs virtual function vs member enum?dynamic_cast vs 虚函数 vs 成员枚举?
【发布时间】:2015-07-31 19:14:08
【问题描述】:

我的问题是指:

C++ dynamic_cast vs storing object type in a static enum?

问题没有得到解答。 dynamic_cast 需要 RTTI,而虚函数需要查表,从而减慢它们的调用速度(我知道 Stroustrup 建议这样做)。枚举 + 访问器是区分类型的最快方法吗?

编辑:

我的帖子的重点是一个课程Screen

class ScreenImpl;

class Screen
{
public:
  enum Type
  {
    GLES1,
    GLES2,
    GLES3
  };

  enum Type type() const noexcept { return type_; }

private:
  enum Type type_;

  ScreenImpl* impl_;
};

这个类可以有不同的实现(使用PIMPL,可以创建3个不同的上下文之一,头文件保持不变,所以static_cast是可以的)我认为对象可能会在什么上下文下查询它们运行(GLES1、GLES2 或 GLES3)。或者,我可以使用 dynamic_cast(至少需要 1 个虚拟成员函数)或 typeid。现在,在阅读了这些帖子之后,我想我会取消这一点,并让所有对象提前知道它们在什么上下文中运行(排除所有 if 和开关,以及虚函数的调用)。

【问题讨论】:

  • 是我还是你想重塑typeid运营商?你想达到什么目的?你需要多态吗?如果需要,你需要动态多态还是静态多态?
  • 由于所有三种技术都在执行大致相同的逻辑,因此它们中的任何一种都不太可能比其他技术快得多。真正让一种方法优于另一种方法的真正原因在于它如何成功(或失败)保持程序的代码随时间可管理和可扩展。
  • 您真正想要完成的是什么?如果它类似于“我想要一个虚拟方法之类的东西,但我认为我可以比编译器更聪明”,那么就停在那里。也许你可以,但几乎可以肯定不值得付出努力和概念上的负担。
  • 你将如何实现“访问器”?使用表格查找? :) 正如提到的链接答案,static 真的没有意义,因为它不是动态的。您必须将类型存储在每个 object...
  • @Jeremy Friesner:太正确了!可管理且清晰的代码是实践中的首要考虑因素。

标签: c++


【解决方案1】:

通常情况下,您需要全部三个... ;)

一般来说,virtual 的减速小于if (x) do_something 的开销。特别是如果你得到多个 if 语句,这将对性能非常不利。换句话说,在性能方面,在每个类中存储一个枚举至少与调用虚函数一样糟糕。

[就像上一个问题中的答案所说,你不能有一个静态枚举,除非所有对象都是相同的类型,在这种情况下,你不是在谈论你应该使用 virtual 的东西]。

在大多数编译器中,dynamic_cast 只需要一个 vtable - 编译器将生成代码以将 vtable 的地址与已知类型进行比较。但是如果你认为比较 vtable + 比较结果不是 NULL 比调用虚函数更好(性能方面,显然设计可能是另一回事),我想你会发现你错了。

如果您因为使用虚函数而遇到性能问题,那么可能是对象本身设计不当/使用不正确,或其他原因。例如,调用虚拟函数 PER PIXEL 可能是个坏主意,您应该使用对像素区域进行操作的函数...

当然,与往常一样,任何与性能有关的事情,您都需要在您的系统上、您的编译器和您的编译器设置上进行测量(不要在调试版本上进行测量) - 编译器通常会做一些不是当您没有优化时代表“真实代码”)。

还要注意,编译器可以(有时)去虚拟化函数。还有更多的工作正在进行中,以便编译器会“更好”地做到这一点。例如,请参阅 Jan Hubicka 关于该主题的帖子: http://hubicka.blogspot.co.uk/2014/01/devirtualization-in-c-part-1.html

【讨论】:

    【解决方案2】:

    如有疑问,请分析并检查组件 :)

    这是三种情况的比较(dynamic_cast、虚拟函数 (type) 和 enum)。为简单起见,special 方法在所有情况下都是相同的,仅在实际情况下才能看到差异。然而,在现实世界中,“特别”每次都会有所不同,否则就没有意义了。

    by_enum 还提供了在开始时 vcall 中发生的情况的演示。

    enum Type { D1_t, D2_t, D3_t, D4_t, D5_t };
    struct Base
    {
      virtual ~Base() = default;
      virtual Type type() = 0;
    };
    
    struct D1 : public Base
    { 
      Type type() override { return D1_t; }
      int special1();
    };
    struct D2 : public Base{ 
      Type type() override { return D2_t; } 
      int special2();
    };
    struct D3 : public D2{
      Type type() override { return D3_t; } 
      int special3();
    };
    struct D4 : public D2{ 
      Type type() override { return D4_t; }
      int special4();
    };
    struct D5 : public D4{
      Type type() override { return D5_t; }
      int special5();
    };
    
    
    int by_dynamic(Base* b)
    {
      if(auto d = dynamic_cast<D1*>(b)) return d->special1();
      else if(auto d = dynamic_cast<D2*>(b)) return d->special2();
      else if(auto d = dynamic_cast<D3*>(b)) return d->special3();
      else if(auto d = dynamic_cast<D4*>(b)) return d->special4();
      else if(auto d = dynamic_cast<D5*>(b)) return d->special5();
    }
    
    int by_enum(Base* b)
    {
      switch(b->type())
      {
        case D1_t:
          return static_cast<D1*>(b)->special1();
          break;
        case D2_t:
          return static_cast<D2*>(b)->special2();
          break;
        case D3_t:
          return static_cast<D3*>(b)->special3();
          break;
        case D4_t:
          return static_cast<D4*>(b)->special4();
          break;
        case D5_t:
          return static_cast<D5*>(b)->special5();
          break;
      }
    }
    

    这是by_dynamic(GCC-5.2、-O3)的相关 ASM。所以我认为,如果你受到性能限制,那就去枚举吧。

    by_dynamic(Base*):
        testq   %rdi, %rdi
        je  .L15
        pushq   %rbx
        xorl    %ecx, %ecx
        movl    typeinfo for D1, %edx
        movl    typeinfo for Base, %esi
        movq    %rdi, %rbx
        call    __dynamic_cast
        testq   %rax, %rax
        je  .L3
        popq    %rbx
        movq    %rax, %rdi
        jmp D1::special1()
    .L3:
        xorl    %ecx, %ecx
        movl    typeinfo for D2, %edx
        movl    typeinfo for Base, %esi
        movq    %rbx, %rdi
        call    __dynamic_cast
        testq   %rax, %rax
        je  .L4
        popq    %rbx
        movq    %rax, %rdi
        jmp D2::special2()
    .L4:
        xorl    %ecx, %ecx
        movl    typeinfo for D3, %edx
        movl    typeinfo for Base, %esi
        movq    %rbx, %rdi
        call    __dynamic_cast
        testq   %rax, %rax
        je  .L5
        popq    %rbx
        movq    %rax, %rdi
        jmp D3::special3()
    .L5:
        xorl    %ecx, %ecx
        movl    typeinfo for D4, %edx
        movl    typeinfo for Base, %esi
        movq    %rbx, %rdi
        call    __dynamic_cast
        testq   %rax, %rax
        je  .L6
        popq    %rbx
        movq    %rax, %rdi
        jmp D4::special4()
    .L6:
        xorl    %ecx, %ecx
        movl    typeinfo for D5, %edx
        movl    typeinfo for Base, %esi
        movq    %rbx, %rdi
        call    __dynamic_cast
        testq   %rax, %rax
        je  .L2
        popq    %rbx
        movq    %rax, %rdi
        jmp D5::special5()
    .L2:
        popq    %rbx
    .L15:
        ret
    

    对于by_enum

    by_enum(Base*):
        pushq   %rbx
        movq    (%rdi), %rax
        movq    %rdi, %rbx
        call    *16(%rax)
        cmpl    $4, %eax
        ja  .L18
        movl    %eax, %eax
        movq    %rbx, %rdi
        jmp *.L20(,%rax,8)
    .L20:
        .quad   .L19
        .quad   .L21
        .quad   .L22
        .quad   .L23
        .quad   .L24
        popq    %rbx
        jmp D4::special4()
        popq    %rbx
        jmp D5::special5()
        popq    %rbx
        jmp D1::special1()
        popq    %rbx
        jmp D2::special2()
        popq    %rbx
        jmp D3::special3()
    .L18:
        popq    %rbx
        ret
    

    【讨论】:

      【解决方案3】:

      Vtable 'lookup' 是偏移量的值(一加运算),然后取消引用指向函数的指针。
      静态枚举将花费几次“如果”检查。如果有很多如果,它可能会花费更多:)但是你应该衡量这些事情。 编译时模板多态性在运行时不会花费您任何费用。

      【讨论】:

      • 不要忘记 RTTI 查找比 vftable 查找复杂得多。另请注意,编译器可以优化 switch 语句以具有 O(1) 复杂度(乘法+跳转),因此您关于枚举的语句不是正确的。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2011-09-02
      • 1970-01-01
      • 1970-01-01
      • 2013-06-25
      • 2023-03-16
      • 2013-09-22
      • 1970-01-01
      相关资源
      最近更新 更多