Rozważmy następujący kod, gdzie B
to wirtualna klasa bazowa dziedziczone przez D
przez B1
i B2
:Błąd listy inicjalizacji w gcc?
#include <iostream>
class B
{
protected:
int x;
protected:
B(int x) : x{x}{std::cout << x << std::endl;}
};
class B1 : virtual public B
{
protected:
B1() : B(0){}
};
class B2 : virtual public B
{
protected:
B2() : B(10){}
};
class D : public B1, public B2
{
public:
D() : B(99), B1(), B2() {}
void print() {std::cout << "Final: " << x << std::endl;}
};
int main() {
D d;
d.print();
return 0;
}
Zobacz przykład here pracy. Używam wyjść w konstruktorze B
i po D
został całkowicie zbudowany, aby śledzić, co się dzieje. Wszystko działa dobrze, kiedy kompiluję powyższy przykład za pomocą g ++ - 4.8.1. Drukuje
99
Final: 99
ponieważ B
s konstruktor nazywa raz z klasy najbardziej pochodne (D
), a także określa się wartość końcową x
.
Teraz przychodzi dziwną część: Gdybym zmienić linię
D() : B(99), B1(), B2() {}
do nowej jednolitej składni inicjalizacji, tj
D() : B{99}, B1{}, B2{} {}
dzieją się dziwne rzeczy. Z jednej strony, to już nie skompilować, z błędem
prog.cpp: In constructor ‘D::D()’:
prog.cpp:17:5: error: ‘B1::B1()’ is protected
B1() : B(0){}
^
prog.cpp:31:27: error: within this context
D() : B{99}, B1{}, B2{} {}
(a tym samym dla B2
patrz here), co nie ma sensu, ponieważ używam go w klasie pochodnej, więc powinno być protected
w porządku. Jeśli mam rację w tym i dokonać konstruktorów B1
i B2
społeczeństwa zamiast chronić, wszystko zostanie całkowicie pomieszane (patrz here), jako wyjście staje
99
0
10
Final: 10
Tak więc, w rzeczywistości części B1
si B2
s konstruktory, które inicjują B
, są nadal wykonywane, a nawet zmieniają wartość x
. Nie powinno tak być w przypadku dziedziczenia wirtualnego. I pamiętaj, że tylko rzeczy zmieniłem są
- publicznego zamiast chronionych konstruktorów w
B1
iB2
- użycie
classname{}
składni w zarejestrował listy inicjalizacjiD
zamiastclassname()
.
Nie mogę uwierzyć, że tak podstawowa rzecz idzie źle w gcc. Ale testowałem to z klangiem na mojej lokalnej maszynie i tam wszystkie trzy przypadki kompilują i działają zgodnie z przeznaczeniem (to jest jak w pierwszym przykładzie powyżej). Jeśli to nie jest błąd, czy ktoś może wskazać mi, czego mi brakuje?
EDYCJA: Moje pierwsze wyszukiwanie jakoś nie przyniosło, ale teraz znalazłem this other question, pokazujące przynajmniej chroniony/publiczny błąd. Jednak był to gcc-4.7, więc spodziewałbym się, że będzie to obsługiwane w gcc-4.8. Czyli powinienem stwierdzić, że listy inicjalizujące są po prostu całkowicie pomieszane w gcc !?
To działa z brzękiem 3.5, ale nie z gcc 4.9.0. Więc to prawdopodobnie czyjś błąd kompilatora. –
Tak, to jest dokładnie to, co powiedziałem w moim pytaniu, z tym wyjątkiem, że wydaje się, że utrzymuje się nawet w gcc-4.9.0! Przeraża mnie to, że może to w milczeniu doprowadzić do wielu wywołań konstruktora dla tego samego obiektu (jeśli wszyscy konstruktorzy są publiczni), naruszając jedną z najbardziej podstawowych zasad OOP ... – Oguk
Mam dokładnie takie same błędy w 'GCC 5.1.1 '. – Galik