C++ “动态”的含义是什么;T*>;(...)`?

C++ “动态”的含义是什么;T*>;(...)`?,c++,c++11,casting,undefined-behavior,dynamic-cast,C++,C++11,Casting,Undefined Behavior,Dynamic Cast,最近,我在查看一个开源项目的代码时,看到了一堆形式为T&object=*dynamic_cast(ptr)的语句 (实际上,这是在宏中发生的,宏用于按照类似的模式声明许多函数。) 对我来说,这就像是一种代码气味。我的理由是,如果你知道演员阵容会成功,那么为什么不使用静态演员阵容?如果您不确定,那么您不应该使用断言进行测试吗?因为编译器可以假定*中的任何指针都不是空的 我向irc上的一位开发人员询问了这件事,他说,他认为static\u castdowncast是不安全的。他们可以添加断言,但即使

最近,我在查看一个开源项目的代码时,看到了一堆形式为
T&object=*dynamic_cast(ptr)的语句

(实际上,这是在宏中发生的,宏用于按照类似的模式声明许多函数。)

对我来说,这就像是一种代码气味。我的理由是,如果你知道演员阵容会成功,那么为什么不使用
静态演员阵容
?如果您不确定,那么您不应该使用断言进行测试吗?因为编译器可以假定
*
中的任何指针都不是空的

我向irc上的一位开发人员询问了这件事,他说,他认为
static\u cast
downcast是不安全的。他们可以添加断言,但即使不添加,他说,当实际使用
obj
时,仍然会出现空指针解引用和崩溃。(因为,在失败时,
dynamic_-cast
会将指针转换为null,然后当您访问任何成员时,您将从某个值非常接近零的地址读取数据,而操作系统不允许这样做。)如果您使用
static_-cast
,并且它坏了,您可能只会得到一些内存损坏。因此,通过使用
*dynamic_cast
选项,您可以在速度上进行权衡,以获得更好的可调试性。您没有为断言付费,相反,您基本上依赖于操作系统来捕获nullptr解引用,至少这是我所理解的

当时我接受了这一解释,但这让我感到困扰,我又仔细考虑了一下

这是我的理由

如果我理解标准的权利,一个
static\u cast
pointer cast基本上意味着做一些固定指针算法。也就是说,如果我有<代码> A*A < /C>,我将它静态地转换成一个相关的类型<代码> B*<代码>,编译器实际上要做的就是给指针添加一些偏移,偏移只取决于类型的布局<代码> A<代码>,<代码> B/COD>,以及潜在的C++实现。这个理论可以通过静态转换指针来测试,指向
void*
,并在静态转换前后输出它们。我希望,如果您查看生成的程序集,
static_cast
将变成“向指针对应的寄存器添加一些固定常量”

一个
dynamic_cast
指针强制转换意味着,首先检查RTTI,并且仅在基于动态类型有效的情况下执行静态强制转换。如果不是,则返回
nullptr
。因此,我希望编译器会在某个时刻将
动态转换(ptr)
的表达式扩展为
ptr
类型为
A*
的表达式,如

(__validate_dynamic_cast_A_to_B(ptr) ? static_cast<B*>(ptr) : nullptr)

根据
gcc 5.3x86程序集,此程序集带有选项
-O3-std=c++11
,如下所示:



当我将
dynamic\u cast
更改为
static\u cast
时,我会得到以下结果:



这与
clang3.8
和相同的选项是一样的

dynamic\u cast



static\u cast



因此,在这两种情况下,
dynamic\u cast
似乎无法被优化器消除:

它似乎使用这两个类的typeinfo生成对神秘的
\uuuuu dynamic\u cast
函数的调用,不管发生什么。即使所有优化都已启用,并且
B
标记为final

    这个低级呼叫有没有我考虑的副作用?我的理解是vtables基本上是固定的,并且对象中的vptr不会改变。。。我说得对吗?我对vtables的实际实现方式只有基本的了解,而且我通常在代码中避免使用虚拟函数,因此我没有对它进行深入思考或积累经验

  • 作为一种有效的优化,一致性编译器可以用
    *静态强制转换(ptr)
    替换
    *动态强制转换(ptr)
    ,对吗

  • “通常”(比方说,在x86机器上,在“通常”复杂度的层次结构中的类之间强制转换)的
    动态\u强制转换
    不能被优化掉,并且事实上会产生
    nullptr
    ,即使您在之后立即执行
    *
    ,在访问对象时导致
    nullptr
    取消引用和崩溃

  • “总是用
    动态强制转换
    +某种测试或断言或
    *静态强制转换(ptr)
    替换
    *动态强制转换(ptr)
    ”是一个合理的建议吗


T&object=*dynamic\u cast(ptr)被破坏,因为它在失败时调用UB,句号。我认为没有必要重复这一点。即使它似乎在当前的编译器上工作,但在具有更积极的优化器的更高版本上也可能不工作

如果您想要检查并且不想为编写断言而烦恼,请使用在失败时抛出
bad\u cast
的引用表单:

T& object = dynamic_cast<T&>(*ptr);
如果实际最派生的对象是a
D
,并且您有一个指向标有
*
a
基的指针,您实际上可以
dynamic\u cast
它来获取指向
B
子对象的指针:

struct A { virtual ~A() = default; };
struct B : A {};
struct C : A {};
struct D : B, C {};
void f() {
    D d;
    C& c = d;
    A& a = c;
    assert(dynamic_cast<B*>(&a) != nullptr);
}
struct A{virtual~A()=default;};
结构B:A{};
结构C:A{};
结构D:B,C{};
void f(){
D;
C&C=d;
A&A=c;
断言(动态强制转换(&a)!=nullptr);
}
请注意,这里的
静态\u cast
是完全错误的

(另一个突出的例子是,
dynamic_-cast
可以做一些
static_-cast
做不到的事情,那就是当你从一个虚拟基类转换到一个派生类时。)

在一个没有
final
或整个程序知识的世界中,您必须在运行时进行检查(因为
C
D
可能对您不可见)。使用
B
上的
final
,您应该可以不做这件事,但如果编译器没有抽出时间选择
A::foo():
        movl    8(%rdi), %eax
        addl    %eax, output(%rip)
        ret
B::foo():
        movl    12(%rdi), %eax
        subl    %eax, output(%rip)
        ret
visit(A*):
        movl    12(%rdi), %eax
        addl    %eax, %eax
        subl    %eax, output(%rip)
        ret
main:
        subq    $8, %rsp
        movl    $16, %edi
        call    operator new(unsigned long)
        movl    $16, %edi
        subl    $20, output(%rip)
        call    operator new(unsigned long)
        movl    12(%rax), %edx
        movl    output(%rip), %eax
        subl    %edx, %eax
        subl    %edx, %eax
        movl    %eax, output(%rip)
        addq    $8, %rsp
        ret
output:
        .zero   4
visit(A*):                            # @visit(A*)
        xorl    %eax, %eax
        testq   %rdi, %rdi
        je      .LBB0_2
        pushq   %rax
        movl    typeinfo for A, %esi
        movl    typeinfo for B, %edx
        xorl    %ecx, %ecx
        callq   __dynamic_cast
        addq    $8, %rsp
.LBB0_2:
        movl    output(%rip), %ecx
        subl    12(%rax), %ecx
        movl    %ecx, output(%rip)
        subl    12(%rax), %ecx
        movl    %ecx, output(%rip)
        retq

B::foo():                            # @B::foo()
        movl    12(%rdi), %eax
        subl    %eax, output(%rip)
        retq

main:                                   # @main
        pushq   %rbx
        movl    $16, %edi
        callq   operator new(unsigned long)
        movl    $5, 8(%rax)
        movq    vtable for B+16, (%rax)
        movl    $10, 12(%rax)
        movl    typeinfo for A, %esi
        movl    typeinfo for B, %edx
        xorl    %ecx, %ecx
        movq    %rax, %rdi
        callq   __dynamic_cast
        movl    output(%rip), %ebx
        subl    12(%rax), %ebx
        movl    %ebx, output(%rip)
        subl    12(%rax), %ebx
        movl    %ebx, output(%rip)
        movl    $16, %edi
        callq   operator new(unsigned long)
        movq    vtable for A+16, (%rax)
        movl    $10, 8(%rax)
        movl    typeinfo for A, %esi
        movl    typeinfo for B, %edx
        xorl    %ecx, %ecx
        movq    %rax, %rdi
        callq   __dynamic_cast
        subl    12(%rax), %ebx
        movl    %ebx, output(%rip)
        subl    12(%rax), %ebx
        movl    %ebx, output(%rip)
        movl    %ebx, %eax
        popq    %rbx
        retq

A::foo():                            # @A::foo()
        movl    8(%rdi), %eax
        addl    %eax, output(%rip)
        retq

output:
        .long   0                       # 0x0

typeinfo name for A:

typeinfo for A:

typeinfo name for B:

typeinfo for B:

vtable for B:

vtable for A:
visit(A*):                            # @visit(A*)
        movl    output(%rip), %eax
        subl    12(%rdi), %eax
        movl    %eax, output(%rip)
        subl    12(%rdi), %eax
        movl    %eax, output(%rip)
        retq

main:                                   # @main
        retq

output:
        .long   0                       # 0x0
T& object = dynamic_cast<T&>(*ptr);
A   A (*)
|   |
B   C
\   /
 \ /
  D
struct A { virtual ~A() = default; };
struct B : A {};
struct C : A {};
struct D : B, C {};
void f() {
    D d;
    C& c = d;
    A& a = c;
    assert(dynamic_cast<B*>(&a) != nullptr);
}