用于堆栈分配对象的C++虚拟析构函数内联

C++ virtual destructor inlining for stack allocated objects

本文关键字:虚拟 析构函数 C++ 堆栈 分配 对象 用于      更新时间:2023-10-16

在隐式销毁堆栈分配的对象的情况下,是否允许编译器内联虚拟析构函数?

我知道"delete"操作必须通过虚拟函数表调用虚拟析构函数(例如,它不能内联),因为它不能知道指针所指的确切类。

但是当在堆栈上分配对象时,编译器知道确切的类。所以我认为内联隐式销毁是免费的,因为它可以看到类的实际销毁器。如果编译器不被允许这样做,那么为什么不呢?什么情况下可以将析构函数重写为与编译器所知道的不同的东西?

是的,编译器在这种情况下可以内联虚拟析构函数。让我们考虑一个代码示例:
#include <iostream>
int global = 0;
class A {
public:
virtual void foo() { std::cout << "A" << std::endl; }
virtual ~A() { ++global; }
};
class B : public A {
public:
virtual void foo() { std::cout << "B" << std::endl; }
virtual ~B() { --global; }
};
int main() {
{
B b[5];
b[0].foo();
}
std::cout << "global: " << global << std::endl;
return 0;
}

https://godbolt.org/g/PWEVW8

正如你所看到的,clang 3.8和-O3优化永远不会为类生成代码(gcc 6.1和-O3将生成类B,但无论如何都会内联析构函数):

main:                                   # @main
pushq   %r14
pushq   %rbx
pushq   %rax
movl    std::cout, %edi
movl    $.L.str.2, %esi
movl    $1, %edx
callq   std::basic_ostream<char, std::char_traits<char> >& std::__ostream_insert<char, std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*, long)
movq    std::cout(%rip), %rax
movq    -24(%rax), %rax
movq    std::cout+240(%rax), %rbx
testq   %rbx, %rbx
je      .LBB0_9
cmpb    $0, 56(%rbx)
je      .LBB0_3
movb    67(%rbx), %al
jmp     .LBB0_4
.LBB0_3:
movq    %rbx, %rdi
callq   std::ctype<char>::_M_widen_init() const
movq    (%rbx), %rax
movl    $10, %esi
movq    %rbx, %rdi
callq   *48(%rax)
.LBB0_4:                                # %_ZNKSt5ctypeIcE5widenEc.exit2
movsbl  %al, %esi
movl    std::cout, %edi
callq   std::basic_ostream<char, std::char_traits<char> >::put(char)
movq    %rax, %rdi
callq   std::basic_ostream<char, std::char_traits<char> >::flush()
movl    std::cout, %edi
movl    $.L.str, %esi
movl    $8, %edx
callq   std::basic_ostream<char, std::char_traits<char> >& std::__ostream_insert<char, std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*, long)
movl    global(%rip), %esi
movl    std::cout, %edi
callq   std::basic_ostream<char, std::char_traits<char> >::operator<<(int)
movq    %rax, %r14
movq    (%r14), %rax
movq    -24(%rax), %rax
movq    240(%r14,%rax), %rbx
testq   %rbx, %rbx
je      .LBB0_9
cmpb    $0, 56(%rbx)
je      .LBB0_7
movb    67(%rbx), %al
jmp     .LBB0_8
.LBB0_7:
movq    %rbx, %rdi
callq   std::ctype<char>::_M_widen_init() const
movq    (%rbx), %rax
movl    $10, %esi
movq    %rbx, %rdi
callq   *48(%rax)
.LBB0_8:                                # %std::ctype<char>::widen(char) const [clone .exit]
movsbl  %al, %esi
movq    %r14, %rdi
callq   std::basic_ostream<char, std::char_traits<char> >::put(char)
movq    %rax, %rdi
callq   std::basic_ostream<char, std::char_traits<char> >::flush()
xorl    %eax, %eax
addq    $8, %rsp
popq    %rbx
popq    %r14
retq
.LBB0_9:
callq   std::__throw_bad_cast()
pushq   %rax
movl    std::__ioinit, %edi
callq   std::ios_base::Init::Init()
movl    std::ios_base::Init::~Init(), %edi
movl    std::__ioinit, %esi
movl    $__dso_handle, %edx
popq    %rax
jmp     __cxa_atexit            # TAILCALL
global:
.long   0                       # 0x0
.L.str:
.asciz  "global: "
.L.str.2:
.asciz  "B"

在隐式销毁堆栈分配对象的情况下,是否允许编译器内联虚拟析构函数?

是。只要它调用对象的正确运行时类型的析构函数。

我知道"delete"操作必须通过虚拟函数表调用虚拟析构函数(例如,它不能内联),因为它不能知道指针所指的确切类。

只有当编译器不知道指针指向的具体类型时,它才能进行虚拟调用。

但是当在堆栈上分配对象时,编译器知道确切的类。

正确。

所以我认为可以自由内联隐式销毁,因为它可以看到类的实际销毁函数。

嗯,在堆栈上分配并不能保证析构函数的定义是可见的。但如果它是可见的,那么您的假设是正确的,编译器可以自由内联。