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);
如果实际最派生的对象是aD
,并且您有一个指向标有*
的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);
}