Questo articolo è la traduzione di un mio post del 2016 intitolato The power of devirtualization.

La devirtualizzazione accade quando il compilatore può decidere a tempo di compilazione quale funzione chiamare, e quindi produrre una chiamata diretta (al posto di una indiretta), o addirittura mettere il codice in linea ed evitare la chiamata. Questo accade anche in C++98/03, ma spesso richiede l’abilitazione di ottimizzazioni o generazione del codice a tempo di linking per permettere al compilatore di capire se è possibile evitare la chiamata indiretta, e anche in questo caso è piuttosto difficile che la rimozione dell’indirezione accada.

Consideriamo questo codice pre-C++11:

class A {
public:
    virtual int value() { return 1; }
};

class B : public A {
public:
    int value() { return 2; }
};

int test(B* b) {
    return b->value() + 11;
}

Durante la compilazione della chiamata al metodo value() nella funzione test(B* b) il compilatore in generale non può assumere se il tipo reale di b sia B oppure una sottoclasse di B, quindi il codice verrà generato con una chiamata indiretta (una chiamata virtuale al metodo value()). In C++11 possiamo aggiungere la parola chiave override, ma questa non influenza la generazione del codice.

Ma quando aggiungiamo la parola chiave a final al metodo (1)

class B : public A {
public:
    int value() final { return 2; }
};

o all’intera classe (2)

class B final : public A {
public:
    int value() override { return 2; }
};

abbiamo la garanzia che nessuno potrà sovrascrivere il metodo (1), o che nessuna sottoclasse può esistere (2), e finirà per devirtualizzare la chiamata, potenzialmente ottimizzando e inserendo la funzione in linea, e trasformare l’intera chiamata in un semplice return 13;.

Senza devirtualizzazione (ovvero senza la keyword final, Visual Studio 2015 (Update 3) produce questo codice assembly:

    sub     rsp, 40
    mov     rax, qword ptr [rcx]
    call    qword ptr [rax]
    add     eax, 11
    add     rsp, 40
    ret

GCC 6.2 produce questo

    mov     rax, qword ptr [rdi]
    mov     rdx, qword ptr [rax]
    cmp     rdx, offset flat:B::value()
    jne     .L12
    mov     eax, 13                       (*)
    ret
.L12:
    sub     rsp, 8
    call    rdx
    add     rsp, 8
    add     eax, 11
    ret

e Clang 3.9.0 produce questo

    push    rax
    mov     rax, qword ptr [rdi]
    call    qword ptr [rax]
    add     eax, 11
    pop     rcx
    ret

Tutti e tre i compilatori generano lo stesso codice assembly quando la devirtualizzazione è possibile:

    mov     eax, 13
    ret

Una nota riguardo GCC: il codice generato è piuttosto complesso, ma in pratica è una devirtualizzazione parziale: il codice compilato ha un caso speciale per chiamare B::value(), per il quale produce il risultato 13 immediatamente (*). Questo credo si basi sul fatto che il metodo è piuttosto semplice, e il compilatore assume che la chiamata venga spesso rediretta verso B::value() (probabilmente questa deduzione è corroborata dalla mancanza di altre sottoclassi nell’unità di compilazione).

Potete visionare il codice generato in differenti versioni di GCC e Clang su Godbolt Compiler Explorer. Scoprirete che quest’ottimizzazione funziona in tutte le  versioni che supportano C++11 (GCC 4.7+ e Clang 3.0+), anche su architetture non x86/x64. La devirtualizzazione viene applicata anche in Visual Studio 2013 (almeno sulla versione testata da me, Update 5), ma non in Visual Studio 2012 (dove la parola chiave final è accettata e produce errori in caso di overload, ma non attiva alcuna ottimizzazione).

Un ultimo chiarimento richiesto via Reddit: l’uso della keyword final permette la devirtualizzazione, ma i compilatori C++ non sono obbligati ad applicare la devirtualizzazione in such cases.

Questo articolo è la traduzione di un mio post del 2016 intitolato The power of devirtualization.

2 Comments


  1. Great article! And interesting the reference to the website http://gcc.godbolt.org/ . I use to show assembly in the debugger, but that could be very helpful too!

    Have a nice day Marco,
    Ste

    Reply

Leave a Reply

Your email address will not be published. Required fields are marked *