Dziwny domyślny pusty konstruktor na wirtualnym zachowaniu dziedziczenia w GCC
Mam następującą sytuację klasy pochodnej z dziedziczeniem wirtualnym do klasy bazowej w moim kodzie:
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 {}
};
Połączyć: https://godbolt.org/z/bn1EY6
GCC (trunk) daje następujący błąd: error: use of deleted function 'Derived::Derived()'
podczas gdy Clang (trunk) kompiluje go bez problemu.
GCC działa, jeśli zmienię konstruktora na Derived() {}
zamiast Derived() = default
lub zdefiniuję pusty konstruktor w klasie Base.
Dlaczego = default
w tym przypadku usunięcie funkcji w GCC?
Odpowiedzi
Norma mówi (najnowsza wersja robocza):
[class.default.ctor]
Domyślny domyślny konstruktor dla klasy X jest definiowany jako usunięty, jeśli:
- X to związek, który ... [[nie dotyczy]]
- X to klasa niezrzeszona, która ma wariant składowy M z ... [[nie dotyczy]]
- dowolny niestatyczny element członkowski danych bez domyślnego inicjatora elementu członkowskiego ([class.mem]) jest typu referencyjnego, [[nie dotyczy]]
- dowolny niezmienny niestatyczny element członkowski danych typu const kwalifikowanego ... [[nie dotyczy]]
- X jest związkiem i ... [[nie dotyczy]]
- X jest klasą nie związkową i wszyscy członkowie dowolnego anonimowego członka związku ... [[nie dotyczy]]
- [dotyczy, jeśli baza jest potencjalnie skonstruowanym podobiektem ] każdy potencjalnie skonstruowany podobiekt, z wyjątkiem niestatycznego elementu danych z inicjatorem nawiasu klamrowego lub równego, ma typ klasy M (lub jego tablicę) i M nie ma domyślnego konstruktora lub rozpoznanie przeciążenia ([over.match]) zastosowane do znalezienia odpowiedniego konstruktora M skutkuje niejednoznacznością lub funkcją, która jest usunięta lub niedostępna z domyślnego konstruktora domyślnego lub
- każdy potencjalnie skonstruowany podobiekt ma typ z destruktorem, który jest usunięty lub niedostępny z domyślnego konstruktora domyślnego. [[nie dotyczy]]
Potencjalnie tylko jedna reguła ma zastosowanie do usuwanego domyślnego konstruktora domyślnego i zależy od tego, czy baza jest potencjalnie skonstruowanym podobiektem .
[specjalny]
W przypadku klasy jej niestatyczne składowe danych, jej niewirtualne bezpośrednie klasy bazowe oraz, jeśli klasa nie jest abstrakcyjna ([class.abstract]), jej wirtualne klasy bazowe nazywane są jej potencjalnie skonstruowanymi podobiektami.
Derived
jest abstrakcyjna (ponieważ nie implementuje wszystkich czystych funkcji wirtualnych) i Base
jest wirtualną bazą, dlatego baza nie jest potencjalnie skonstruowanym podobiektem, a zatem jedyna reguła, która w przeciwnym razie obowiązywałaby dla usuwanego domyślnego konstruktora, nie ma zastosowania i dlatego nie należy go usuwać. Kompilator jest zły.
Prostym obejściem (oprócz tych, o których już wspomniałeś) jest brak deklaracji Derived::Derieved()
. Wydaje się, że w tym przypadku jest on generowany domyślnie.
Dodanie noexcept powoduje błąd wewnętrzny błąd kompilatora
Jest to również błąd kompilatora.
Dlaczego w tym przypadku = default usuwa funkcję w GCC?
To, czy jest to błąd w GCC (MSVC zachowuje się podobnie, ale clang-cl akceptuje kod w takiej postaci, w jakiej jest), jest kwestią dla tych, którzy są bardziej studiowani w standardach C ++. Wygląda jednak na to, że kompilator przyjmuje = default
, że Derived
konstruktor zależy (lub jest równoważny ) z domyślnym konstruktorem dla Base
- który jest zdecydowanie usuwany, ponieważ zdefiniowano inny (inny niż domyślny) konstruktor.
Jednak jawne dodanie własnego domyślnego konstruktora, Derived() {}
usuwa tę domniemaną zależność.
Potwierdza się to (w GCC i MSVC) przez określenie (czyli cofnięcie usunięcia) domyślnego konstruktora dla Base
klasy:
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 {}
};
EDYCJA : Może to być również istotne, a nawet możliwe duplikaty: Dlaczego domyślny konstruktor jest wywoływany w dziedziczeniu wirtualnym?