Seltsamer leerer Standardkonstruktor für ein virtuelles Vererbungsverhalten in GCC
Ich habe die folgende Situation einer abgeleiteten Klasse mit virtueller Vererbung an eine Basisklasse in meinem Code:
class Base {
int x;
public:
Base(int x): x{x} {}
virtual void f() = 0;
};
class Derived : public virtual Base {
public:
Derived() = default;
};
class Concrete: public Derived {
public:
Concrete(): Base{42} {}
void f() override {}
};
Verknüpfung: https://godbolt.org/z/bn1EY6
GCC (Trunk) gibt den folgenden Fehler aus: error: use of deleted function 'Derived::Derived()'
Clang (Trunk) kompiliert ihn problemlos.
GCC funktioniert, wenn ich den Konstruktor in Derived() {}
anstelle der Derived() = default
Basisklasse ändere oder einen leeren Konstruktor definiere.
Warum wird = default
in diesem Fall die Funktion in GCC entfernt?
Antworten
Standard sagt (letzter Entwurf):
[class.default.ctor]
Ein standardmäßiger Standardkonstruktor für Klasse X wird als gelöscht definiert, wenn:
- X ist eine Vereinigung, die ... [[gilt nicht]]
- X ist eine nicht gewerkschaftlich organisierte Klasse mit einem varianten Mitglied M mit ... [[gilt nicht]]
- Jedes nicht statische Datenelement ohne Standardelementinitialisierer ([class.mem]) ist vom Referenztyp [[gilt nicht]].
- jedes nichtvariante nicht statische Datenelement vom Typ const-qualifizierter Typ ... [[gilt nicht]]
- X ist eine Vereinigung und ... [[gilt nicht]]
- X ist eine Nicht-Gewerkschaftsklasse und alle Mitglieder eines anonymen Gewerkschaftsmitglieds ... [[gilt nicht]]
- [gilt, wenn die Basis ein potenziell konstruiertes Unterobjekt ist ] Jedes potenziell konstruierte Unterobjekt mit Ausnahme eines nicht statischen Datenelements mit einem Klammer- oder Gleichheitsinitialisierer hat den Klassentyp M (oder ein Array davon) und entweder M hat keinen Standardkonstruktor oder eine Überladungsauflösung ([over.match]), die angewendet wird, um den entsprechenden Konstruktor von M zu finden, führt zu einer Mehrdeutigkeit oder zu einer Funktion, die vom standardmäßigen Standardkonstruktor gelöscht wird oder auf die nicht zugegriffen werden kann, oder
- Jedes potenziell erstellte Unterobjekt hat einen Typ mit einem Destruktor, auf den der standardmäßige Standardkonstruktor gelöscht wird oder auf den nicht zugegriffen werden kann. [[gilt nicht]]
Für den gelöschten Standardkonstruktor gilt möglicherweise nur eine Regel. Dies hängt davon ab, ob es sich bei der Basis um ein potenziell erstelltes Unterobjekt handelt .
[Besondere]
Für eine Klasse werden ihre nicht statischen Datenelemente, ihre nicht virtuellen direkten Basisklassen und, wenn die Klasse nicht abstrakt ist ([class.abstract]), ihre virtuellen Basisklassen als potenziell konstruierte Unterobjekte bezeichnet.
Derived
ist abstrakt (weil nicht alle reinen virtuellen Funktionen implementiert sind) und Base
eine virtuelle Basis. Daher ist die Basis kein potenziell konstruiertes Unterobjekt, und daher gilt die einzige Regel, die ansonsten für den zu löschenden Standardkonstruktor gelten würde, nicht und daher sollte es nicht gelöscht werden. Der Compiler ist falsch.
Eine einfache Problemumgehung (außer den bereits erwähnten) besteht darin, überhaupt keine Erklärung Derived::Derieved()
abzugeben. Es scheint in diesem Fall implizit korrekt generiert zu werden.
Das Hinzufügen des noexcept ergibt den Fehler interner Compilerfehler
Dies ist auch ein Compiler-Fehler.
Warum entfernt die = Standardeinstellung in diesem Fall die Funktion in GCC?
Ob dies ein Fehler in GCC ist oder nicht (MSVC verhält sich ähnlich, aber clang-cl akzeptiert den Code so wie er ist), ist Sache derjenigen, die in den C ++ - Standards genauer untersucht werden. Es scheint jedoch, dass der Complier davon ausgeht = default
, dass der Derived
Konstruktor vom Standardkonstruktor abhängt (oder diesem entspricht ) Base
- was definitiv gelöscht wird, da Sie einen anderen (nicht standardmäßigen) Konstruktor definiert haben.
Wenn Sie jedoch explizit Ihren eigenen Standardkonstruktor hinzufügen , Derived() {}
wird diese implizite Abhängigkeit entfernt.
Dies wird (in GCC und MSVC) bestätigt, indem der Standardkonstruktor für die Base
Klasse angegeben (dh wiederhergestellt) wird :
class Base {
int x;
public:
Base() : x{0} {} // Adding this removes the error!
// Base() = default; // Also works
Base(int x): x{x} {}
virtual void f() = 0;
};
class Derived : public virtual Base {
public:
Derived() = default;
};
class Concrete: public Derived {
public:
Concrete(): Base{42} {}
void f() override {}
};
BEARBEITEN : Dies kann auch relevant sein oder sogar ein mögliches Duplikat: Warum wird der Standardkonstruktor bei der virtuellen Vererbung aufgerufen?