Was bedeuten Clangs Schwache Tabellen?

78

Grundsätzlich verstehe ich Clangs nicht -Wweak-vtables. Folgendes habe ich bisher beobachtet:

Fall eins: (löst die Warnung aus)

class A {
    public:
    virtual ~A(){}        
};

class B : public A {
    public:
    virtual ~B(){}
};

int main(){}

Fall zwei: (löst keine Warnung aus)

class A {
    public:
    virtual ~A(){}        
};   

int main(){}

Fall drei: (Löst keine Warnung aus)

class A {
    public:
    virtual ~A();

};

A::~A(){}

class B : public A {
    public:
    virtual ~B(){}
};

int main(){}

Fall vier: (löst eine Warnung aus)

class A {
    public:
    virtual ~A(){}
    virtual void fun(){}        
};    

class B : public A {
    public:
    virtual ~B(){}
};

int main(){}

Fall fünf: (Löst keine Warnung aus)

class A {
    public:
    virtual ~A(){}
    virtual void fun();      
};    

class B : public A {
    public:
    virtual ~B(){}
};

int main(){}

Fall 6: (Löst keine Warnung aus)

class A {
    public:
    virtual ~A(){}
    virtual void fun(){}
};    

class B : public A {};

int main(){}

Fall sieben: (Löst keine Warnung aus)

class A {
    public:
    virtual ~A(){}
    virtual void fun(){}
};    

class B : public A {
    public:
    virtual void fun(){}
};

int main(){}

Die genaue Warnung ist

warning: 'A' has no out-of-line virtual method definitions; its vtable 
will be emitted in every translation unit [-Wweak-vtables]

Wenn ich also anscheinend keine virtuelle Inline-Funktion in einer Klasse deklariere, verursacht dies genau dann ein Problem, wenn ich davon abgeleitet bin und die abgeleitete Klasse einen virtuellen Destruktor hat.

Fragen:

  1. Warum ist das ein Problem?
  2. Warum wird dies durch Deklarieren einer virtuellen Funktion behoben? (Warnung spricht von Definitionen)
  3. Warum tritt die Warnung nicht auf, wenn ich nicht von der Klasse abgeleitet bin?
  4. Warum tritt die Warnung nicht auf, wenn die abgeleitete Klasse keinen virtuellen Destruktor hat?
Baum mit Augen
quelle

Antworten:

102

Wenn alle virtualMethoden einer Klasse inline sind, kann der Compiler keine Übersetzungseinheit auswählen, in der eine einzelne freigegebene Kopie der vtable abgelegt werden soll. Stattdessen muss eine Kopie der vtable in jeder Objektdatei abgelegt werden, die sie benötigt. Auf vielen Plattformen kann der Linker diese mehreren Kopien vereinheitlichen, indem er entweder doppelte Definitionen verwirft oder alle Verweise einer Kopie zuordnet. Dies ist also nur eine Warnung.

Durch das Offline-Implementieren einer virtualFunktion kann der Compiler die Übersetzungseinheit auswählen, die diese Outline-Methode als "Home" für die Implementierungsdetails der Klasse implementiert, und die einzelne gemeinsam genutzte Kopie der vtable in derselben Übersetzungseinheit ablegen . Wenn mehrere Methoden nicht korrekt sind, kann der Compiler eine beliebige Methodenauswahl treffen, sofern diese Auswahl nur durch die Deklaration der Klasse bestimmt wird. Beispielsweise wählt GCC die erste Nicht-Inline-Methode in der Reihenfolge der Deklaration.

Wenn Sie keine Methode einer Klasse überschreiben, hat das virtualSchlüsselwort keine beobachtbare Auswirkung, sodass der Compiler keine vtable für die Klasse ausgeben muss. Wenn Sie nicht vom ADestruktor einer abgeleiteten Klasse ableiten oder diesen nicht deklarieren virtual, sind keine überschriebenen Methoden darin enthalten, Aund daher wird Adie vtable weggelassen. Wenn Sie eine zusätzliche Out-of-Line- virtualMethode deklarieren , um die Warnung zu unterdrücken, und auch etwas tun, das eine Methode überschreibt A, muss die Implementierung der Nicht-Inline- Methode virtual(und der zugehörigen Kopie der vtable) in einer verknüpften Übersetzungseinheit bereitgestellt werden Andernfalls schlägt die Verknüpfung fehl, da die vtable fehlt.

Jeffrey Hantin
quelle
2
Was macht es, wenn (verschiedene) Out-of-Line-Methoden derselben Klasse in mehreren verschiedenen TUs auftreten?
MM
2
@Matt, eine Out-of-Line-Methode wird auf willkürliche, aber deterministische Weise basierend auf der Deklaration der Klasse ausgewählt, und die vtable endet in derselben TU wie die Methodenimplementierung. Alle relevanten TUs sehen die gleiche Deklaration: Abgesehen von inkonsistenten Deklarationsshenanigans werden sie sich auf die gewählte Methode einigen und somit gemeinsam genau eine vtable ausgeben.
Jeffrey Hantin
Diese Antwort erklärt nicht die Fälle 6 und 7.
Leon
2
@ JeffreyHantin Was meinst du mit "B's Destruktor ist nicht virtuell" ? Der vom Compiler generierte (implizit deklarierte) Destruktor von Bsollte virtuell sein (da der Destruktor der Basisklasse deklariert wurde virtual).
Leon
2
@Leon, sagt die Spezifikation: "Ein Destruktor, der standardmäßig und nicht als gelöscht definiert ist, wird implizit definiert, wenn er odr-verwendet wird (3.2) oder wenn er nach seiner ersten Deklaration explizit standardmäßig verwendet wird." In den Fällen 6 oder 7 werden ~B()keine Standardeinstellungen verwendet oder explizit verwendet , daher wird die Standarddefinition vom Compiler nicht implizit instanziiert. Wie kann es virtuell sein, wenn es nicht existiert? Die Trivialität der Beispiele verursacht einige kontraintuitive Effekte.
Jeffrey Hantin