2014-11-13 19 views
12

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

  • publicznego zamiast chronionych konstruktorów w B1 i B2
  • użycie classname{} składni w zarejestrował listy inicjalizacji D zamiast classname().

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 !?

+2

To działa z brzękiem 3.5, ale nie z gcc 4.9.0. Więc to prawdopodobnie czyjś błąd kompilatora. –

+0

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

+1

Mam dokładnie takie same błędy w 'GCC 5.1.1 '. – Galik

Odpowiedz

3

Nie wiem, czy jest za późno na odpowiedź, ale twój kod kompiluje się dobrze w GCC 4.9.2!

~$g++ -std=c++11 test.cpp 
~$./a.out 
99 
Final: 99 

~$gcc --version 
gcc (GCC) 4.9.2 
Copyright (C) 2014 Free Software Foundation, Inc. 
This is free software; see the source for copying conditions. There is NO 
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 
1

Odnośnie wielu połączeń wirtualnych konstruktora klasy bazowej: mogę odtworzyć problem z następującego kodu (z GCC 5.1.0).

#include <iostream> 

struct V { 
    V(){std::cout << "V()\n";} 
}; 

struct A : virtual V { 
    A() : V{} {std::cout << "A()\n";} 
}; 

struct B : A { 
    B(): V{}, A{} {std::cout << "B()\n";} 
}; 

int main(int argc, char **argv) { 
    B b{}; 
} 

Powoduje to następujące dane wyjściowe:

V() 
V() 
A() 
B() 

I nie sądzę, że to jest poprawna accoring do standardu C++:

[class.base.init]/7

... Inicjalizacja wykonywana przez każdy inicjator pamięci stanowi pełne wyrażenie. Każde wyrażenie w inicjatorze pamięci jest oceniane jako część pełnego wyrażenia, które wykonuje inicjalizację. Element inicjujący pamięć , w którym id-inicjatora-id oznacza wirtualną klasę bazową, jest ignorowany podczas wykonywania konstruktora dowolnej klasy, która nie jest klasą najbardziej pochodną.

Gdy wywołanie konstruktora A zostanie zmienione na nawias zamiast klamr, wynikowy plik wykonywalny działa zgodnie z oczekiwaniami i tylko raz wywołuje V().

Został utworzony raport o błędzie dla GCC dotyczące tej kwestii: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=70818

Edit: Brakowało mi, że już była o tym raport o błędzie: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=55922

Powiązane problemy