2016-05-27 8 views
2

Ist es einem Compiler erlaubt, einen virtuellen Destruktor im Falle der impliziten Zerstörung von stack allocated objects einzubinden?C++ virtueller Destruktor Inlining für Stack allokierte Objekte

Ich verstehe, die Operation 'löschen' muss den virtuellen Destruktor über die virtuelle Funktionstabelle aufrufen (z. B. kann es nicht inline), da es die genaue Klasse, auf die der Zeiger verweist, nicht kennen kann.

Aber wenn ein Objekt auf dem Stapel zugeordnet ist, kennt der Compiler die genaue Klasse. Also hätte ich gedacht, dass es frei wäre, die implizite Zerstörung einzubinden, da es die tatsächlichen Destruktoren für die Klasse sehen kann. Wenn der Compiler dies nicht tun darf, warum nicht? Welches Szenario kann den Destruktor auf etwas anderes überschreiben, als der Compiler weiß?

+0

Ist das ein tatsächliches Problem, das Sie mit Code demonstrieren könnten, oder ist Ihre Frage, warum bestimmte Optimierungen nicht gemacht werden? – Soren

+1

Der Compiler kann nach Möglichkeit jede virtuelle Funktion (Inline, Elide, ...) optimieren. –

+0

Sehen Sie sich * devirtualization * an. – Jarod42

Antwort

3

Ja, Compiler können Inline Virtual Destructor in diesem Fall. Lassen Sie uns ein Codebeispiel betrachten:

#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

Wie Sie Klirren 3.8 mit O3 Optimierung sehen nicht immer Code für Klassen (gcc 6.1 mit O3 generieren Klasse B erzeugen, wird aber Inline destructor sowieso):

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" 
1

ist ein Compiler einen virtuellen Destruktor im Fall der impliziten Zerstörung des Stapel zugeordneten Objekte Inline erlaubt?

Ja. Solange es den Destruktor des korrekten Laufzeittyps des Objekts aufruft.

Ich verstehe, dass die Operation 'delete' den virtuellen Destruktor über die virtuelle Funktionstabelle aufrufen muss (z. B. kann sie nicht inline), da sie nicht die genaue Klasse kennen kann, auf die der Zeiger verweist.

Nur wenn der Compiler kann nicht wissen, was der Zeiger konkreter bezieht, muss es einen virtuellen Anruf.

Aber wenn ein Objekt auf dem Stapel zugeordnet ist, kennt der Compiler die genaue Klasse.

Korrekt.

Also ich hätte gedacht, es wäre frei, die implizite Zerstörung inline zu integrieren, da es die tatsächlichen Destruktoren für die Klasse sehen kann.

Nun, die Zuweisung auf dem Stack garantiert nicht, dass die Definition des Destruktors sichtbar ist. Aber wenn es sichtbar ist, dann ist Ihre Annahme richtig, der Compiler ist frei inline.