Standard C ++ (sekcja 8.5) mówi:
Jeśli program wywołuje domyślną inicjalizację obiektu typu T z kwalifikowaną wartością stałą, T będzie typem klasy z domyślnym konstruktorem dostarczonym przez użytkownika.
Czemu? Nie przychodzi mi do głowy żaden powód, dla którego w tym przypadku wymagany jest konstruktor dostarczony przez użytkownika.
struct B{
B():x(42){}
int doSomeStuff() const{return x;}
int x;
};
struct A{
A(){}//other than "because the standard says so", why is this line required?
B b;//not required for this example, just to illustrate
//how this situation isn't totally useless
};
int main(){
const A a;
}
a
, ale gcc-4.3.4 akceptuje to nawet wtedy, gdy to zrobisz (patrz ideone.com/uHvFS )a
. Comeau generuje błąd „zmienna stała” a wymaga inicjatora - klasa „A” nie ma jawnie zadeklarowanego domyślnego konstruktora ”, jeśli linia jest wykomentowana.const A a{}
:)Odpowiedzi:
Uznano to za wadę (w stosunku do wszystkich wersji standardu) i rozwiązała ją Core Working Group (CWG) Defect 253 . Nowe brzmienie standardu to http://eel.is/c++draft/dcl.init#7
To sformułowanie zasadniczo oznacza, że działa oczywisty kod. Jeśli zainicjujesz wszystkie swoje bazy i członków, możesz powiedzieć
A const a;
niezależnie od tego, jak lub czy przeliterujesz jakiekolwiek konstruktory.gcc akceptuje to od 4.6.4. clang zaakceptował to od wersji 3.9.0. Visual Studio również to akceptuje (przynajmniej w 2017 roku, nie wiem, czy wcześniej).
źródło
struct A { int n; A() = default; }; const A a;
, pozwalając,struct B { int n; B() {} }; const B b;
ponieważ nowe sformułowanie nadal mówi „dostarczone przez użytkownika”, a nie „zadeklarowane przez użytkownika”, a ja drapię się po głowie, dlaczego komitet zdecydował się wykluczyć z tego DR konstruktory z jawnie domyślnymi ustawieniami domyślnymi, zmuszając nas do nasze klasy są nietrywialne, jeśli chcemy mieć obiekty const z niezainicjowanymi składowymi.MyPOD
bycia PODstruct
,static MyPOD x;
- opierając się na zero inicjalizacji (jest to słuszna?), Aby ustawić zmienną (y) Użytkownik odpowiednio - kompiluje, alestatic const MyPOD x;
tego nie robi. Czy jest szansa, że zostanie to naprawione?Powodem jest to, że jeśli klasa nie ma konstruktora zdefiniowanego przez użytkownika, może to być POD, a klasa POD nie jest domyślnie inicjowana. Więc jeśli zadeklarujesz stały obiekt POD, który jest niezainicjalizowany, po co z tego? Myślę więc, że standard wymusza tę zasadę, aby obiekt faktycznie był użyteczny.
Ale jeśli ustawisz klasę jako inną niż POD:
Zwróć uwagę na różnicę między POD i bez POD.
Konstruktor zdefiniowany przez użytkownika jest jednym ze sposobów uczynienia klasy inną niż POD. Możesz to zrobić na kilka sposobów.
Zauważ, że nonPOD_B nie ma zdefiniowanego konstruktora zdefiniowanego przez użytkownika. Skompiluj to. Skompiluje:
I skomentuj funkcję wirtualną, a następnie zgodnie z oczekiwaniami podaje błąd:
Myślę, że źle zrozumiałeś ten fragment. Najpierw mówi to (§ 8.5 / 9):
Mówi o typie bez klasy POD, który może kwalifikować się jako CV . Oznacza to, że obiekt inny niż POD powinien być inicjalizowany domyślnie, jeśli nie określono inicjatora. A co to jest inicjalizacja domyślna ? W przypadku braku POD, specyfikacja mówi (§8.5 / 5),
Po prostu mówi o domyślnym konstruktorze T, niezależnie od tego, czy jego zdefiniowany przez użytkownika, czy wygenerowany przez kompilator jest nieistotny.
Jeśli masz jasność co do tego, zrozum, co mówi dalej specyfikacja ((§8.5 / 9),
Tak więc ten tekst sugeruje, że program będzie źle sformułowany, jeśli obiekt jest typu POD o stałej kwalifikacji i nie określono inicjatora (ponieważ POD nie są domyślnie zainicjowane):
Nawiasem mówiąc, kompiluje się dobrze , ponieważ nie jest to POD i może być inicjalizowany domyślnie .
źródło
nonPOD_B
nie ma domyślnego konstruktora podanego przez użytkownika, więc wierszconst nonPOD_B b2
nie jest dozwolony.B
w pytaniu). Jednak w tym przypadku nadal wymagany jest domyślny konstruktor dostarczony przez użytkownika.const
na zainicjowanie obiektu innego niż POD przez wywołanie domyślnego konstruktora wygenerowanego przez kompilator.Czysta spekulacja z mojej strony, ale weź pod uwagę, że inne typy również mają podobne ograniczenie:
Tak więc ta reguła jest nie tylko spójna, ale także (rekurencyjnie) zapobiega zjednostkowanym
const
(pod) obiektom:Jeśli chodzi o drugą stronę pytania (zezwalając na typy z domyślnym konstruktorem), myślę, że idea jest taka, że typ z domyślnym konstruktorem dostarczonym przez użytkownika powinien zawsze znajdować się w jakimś sensownym stanie po skonstruowaniu. Zwróć uwagę, że reguły, jakie są, pozwalają na:
Wtedy być może moglibyśmy sformułować regułę w rodzaju „przynajmniej jeden element członkowski musi być rozsądnie zainicjowany w domyślnym konstruktorze dostarczonym przez użytkownika”, ale to zbyt dużo czasu spędzonego na próbach ochrony przed Murphym. C ++ ma tendencję do ufania programiście w pewnych punktach.
źródło
A(){}
błąd zniknie, więc nic nie zapobiega. Reguła nie działa rekurencyjnie -X(){}
w tym przykładzie nigdy nie jest potrzebna.Oglądałem przemówienie Timura Doumlera na Meeting C ++ 2018 i w końcu zdałem sobie sprawę, dlaczego standard wymaga tutaj konstruktora dostarczonego przez użytkownika, a nie tylko zadeklarowanego przez użytkownika. Ma to związek z regułami inicjalizacji wartości.
Rozważ dwie klasy:
A
ma konstruktora zadeklarowanegoB
przez użytkownika , ma konstruktora dostarczonego przez użytkownika :Na pierwszy rzut oka możesz pomyśleć, że ci dwaj konstruktorzy będą się zachowywać tak samo. Ale zobacz, jak inicjalizacja wartości zachowuje się inaczej, podczas gdy tylko domyślna inicjalizacja zachowuje się tak samo:
A a;
jest domyślną inicjalizacją: element członkowski nieint x
został zainicjowany .B b;
jest domyślną inicjalizacją: element członkowski nieint x
został zainicjowany .A a{};
jest inicjalizacją wartości: element członkowskiint x
jest inicjalizowany przez zero .B b{};
jest inicjalizacją wartości: element członkowski nieint x
został zainicjowany .Teraz zobacz, co się stanie, gdy dodamy
const
:const A a;
jest domyślną inicjalizacją: jest źle sformułowana z powodu reguły przytoczonej w pytaniu.const B b;
jest domyślną inicjalizacją: element członkowski nieint x
został zainicjowany .const A a{};
jest inicjalizacją wartości: element członkowskiint x
jest inicjalizowany przez zero .const B b{};
jest inicjalizacją wartości: element członkowski nieint x
został zainicjowany .Niezainicjalizowany
const
skalar (np. Elementint x
członkowski) byłby bezużyteczny: pisanie do niego jest źle sformułowane (ponieważ jestconst
), a czytanie z niego to UB (ponieważ posiada nieokreśloną wartość). Tak więc zasada ta zapobiega tworzeniu takiego, zmuszając cię do albo dodać initialiser lub opt-in do niebezpiecznych zachowań dodając konstruktora użytkownika warunkiem.Myślę, że byłoby miło mieć taki atrybut,
[[uninitialized]]
który mówi kompilatorowi, kiedy celowo nie inicjalizujesz obiektu. Wtedy nie bylibyśmy zmuszeni uczynić naszej klasy niebędącą trywialnie domyślną konstrukcją, aby obejść ten narożny przypadek. Ten atrybut został faktycznie zaproponowany , ale tak jak wszystkie inne standardowe atrybuty, nie narzuca on żadnego normatywnego zachowania, będąc jedynie wskazówką dla kompilatora.źródło
Gratulacje, wymyśliłeś przypadek, w którym nie musi istnieć żaden konstruktor zdefiniowany przez użytkownika dla
const
deklaracji bez inicjatora, który miałby sens.Czy możesz teraz wymyślić rozsądne przeredagowanie zasady, która obejmuje twoją sprawę, ale nadal sprawia, że sprawy, które powinny być nielegalne, są nielegalne? Czy jest mniej niż 5 lub 6 akapitów? Czy jest łatwe i oczywiste, jak należy je zastosować w każdej sytuacji?
Uważam, że wymyślenie reguły, która pozwoli, aby deklaracja, którą stworzyłeś, miała sens, jest naprawdę trudna, a upewnienie się, że reguła może być stosowana w sposób, który ma sens dla ludzi podczas czytania kodu, jest jeszcze trudniejsze. Wolałbym nieco restrykcyjną regułę, która w większości przypadków była właściwa, od bardzo zniuansowanej i złożonej reguły, która była trudna do zrozumienia i zastosowania.
Pytanie brzmi, czy istnieje nieodparty powód, dla którego reguła powinna być bardziej złożona? Czy istnieje kod, który w innym przypadku byłby bardzo trudny do napisania lub zrozumienia, a który można napisać o wiele prościej, jeśli reguła jest bardziej złożona?
źródło
const POD x;
nielegalnym tak samo, jakconst int x;
jest nielegalny (co ma sens, ponieważ jest bezużyteczny dla POD), ale stałby sięconst NonPOD x;
legalny (co ma sens, ponieważ może mieć podobiekty zawierające przydatne konstruktory / destruktory lub sam przydatny konstruktor / destruktor) .struct NonPod { std::string s; }; const NonPod x;
I wyświetla błąd, gdy NonPod tostruct NonPod { int i; std::string s; }; const NonPod x;