Nie jestem skłonny używać list inicjujących członków z moimi konstruktorami ... ale już dawno zapomniałem o przyczynach tego ...
Czy używasz list inicjujących członków w swoich konstruktorach? Jeśli tak, to dlaczego? Jeśli nie, dlaczego nie?
c++
oop
object-construction
paxos1977
źródło
źródło
Odpowiedzi:
Dla członków klasy POD nie ma znaczenia, to tylko kwestia stylu. W przypadku członków klasy, które są klasami, unika to niepotrzebnego wywołania domyślnego konstruktora. Rozważać:
W tym przypadku konstruktor
B
wywoła domyślny konstruktor dlaA
, a następnie zainicjujea.x
na 3. Lepszym sposobem byłoby, abyB
konstruktor bezpośrednio wywołałA
konstruktor na liście inicjalizatora:Ten nazwałbym tylko
A
„sA(int)
konstruktora, a nie jego konstruktora domyślnego. W tym przykładzie różnica jest nieistotna, ale wyobraź sobie, żeA
to domyślny konstruktor zrobił więcej, na przykład przydzielając pamięć lub otwierając pliki. Nie chciałbyś tego robić niepotrzebnie.Ponadto, jeśli klasa nie ma domyślnego konstruktora lub masz
const
zmienną składową, musisz użyć listy inicjalizującej:źródło
Poza wymienionymi powyżej przyczynami wydajności, jeśli twoja klasa przechowuje odniesienia do obiektów przekazanych jako parametry konstruktora lub twoja klasa ma zmienne const, nie masz innego wyboru, jak tylko użycie list inicjalizujących.
źródło
Jednym z ważnych powodów używania listy inicjalizacyjnej konstruktora, o której nie wspomniano w odpowiedziach tutaj, jest inicjalizacja klasy podstawowej.
Zgodnie z kolejnością budowy klasa podstawowa powinna zostać zbudowana przed klasą potomną. Bez listy inicjalizacyjnej konstruktora jest to możliwe, jeśli klasa podstawowa ma domyślny konstruktor, który zostanie wywołany tuż przed wejściem do konstruktora klasy potomnej.
Ale jeśli twoja klasa podstawowa ma tylko sparametryzowany konstruktor, musisz użyć listy inicjalizującej konstruktora, aby upewnić się, że twoja klasa podstawowa została zainicjowana przed klasą potomną.
Inicjalizacja podobiektów, które mają sparametryzowane konstruktory
Wydajność
Korzystając z listy inicjalizacyjnej konstruktora, inicjujesz członków danych dokładnie do stanu, którego potrzebujesz w kodzie, zamiast najpierw inicjować ich do stanu domyślnego, a następnie zmieniać ich stan na potrzebny w kodzie.
Jeśli elementy stałej danych w postaci niestatycznej w twojej klasie mają domyślne konstruktory i nie używasz listy inicjalizującej konstruktora, nie będziesz w stanie zainicjować ich do zamierzonego stanu, ponieważ zostaną one zainicjowane do stanu domyślnego.
Członkowie danych referencyjnych muszą zostać zainicjalizowani, gdy kompilator wchodzi do konstruktora, ponieważ referencje nie mogą być po prostu deklarowane i inicjowane później. Jest to możliwe tylko z listą inicjatora konstruktora.
źródło
Oprócz problemów z wydajnością jest jeszcze jedna bardzo ważna, którą nazwałbym utrzymywalnością i rozszerzalnością kodu.
Jeśli T jest POD i zaczynasz preferować listę inicjującą, to jeśli raz T zmieni się na typ inny niż POD, nie będziesz musiał nic zmieniać wokół inicjalizacji, aby uniknąć niepotrzebnych wywołań konstruktora, ponieważ jest już zoptymalizowany.
Jeśli typ T ma domyślny konstruktor i co najmniej jeden konstruktor zdefiniowany przez użytkownika i jeden raz zdecydujesz się usunąć lub ukryć domyślny konstruktor, to jeśli użyto listy inicjalizacyjnej, nie musisz aktualizować kodu, jeśli konstruktory zdefiniowane przez użytkownika, ponieważ są już poprawnie wdrożone.
To samo z stałymi elementami lub elementami odniesienia, powiedzmy początkowo T jest zdefiniowane następująco:
Następnie decydujesz się zakwalifikować jako const, jeśli użyjesz listy inicjalizacyjnej od początku, to była to zmiana w jednej linii, ale mając T zdefiniowaną jak wyżej, musisz również wykopać definicję konstruktora, aby usunąć przypisanie:
Nie jest tajemnicą, że konserwacja jest znacznie łatwiejsza i mniej podatna na błędy, jeśli kod nie został napisany przez „małpę kodu”, ale przez inżyniera, który podejmuje decyzje w oparciu o głębsze przemyślenia na temat tego, co robi.
źródło
Przed uruchomieniem treści konstruktora wywoływane są wszystkie konstruktory dla jego klasy nadrzędnej, a następnie dla jej pól. Domyślnie wywoływane są konstruktory bez argumentów. Listy inicjujące pozwalają wybrać, który konstruktor ma być wywoływany i jakie argumenty otrzymuje ten konstruktor.
Jeśli masz odwołanie lub pole stałej, lub jeśli jedna z używanych klas nie ma domyślnego konstruktora, musisz użyć listy inicjalizacji.
źródło
Tutaj kompilator wykonuje następujące kroki, aby utworzyć obiekt typu MyClass
1. Konstruktor typu jest wywoływany jako pierwszy dla „a”.
2. Operator przypisania „Typu” jest wywoływany w treści konstruktora MyClass () w celu przypisania
A potem w końcu destruktor „Typu” jest nazywany „a”, ponieważ wychodzi poza zakres.
Teraz rozważ ten sam kod z konstruktorem MyClass () z listą inicjującą
W przypadku listy inicjalizującej kompilator wykonuje następujące kroki:
źródło
Wystarczy dodać dodatkowe informacje, aby wykazać, jak dużą różnicę może sprawić lista inicjująca członków . W leetcode 303 Zapytanie o sumę zakresu - niezmienne, https://leetcode.com/problems/range-sum-query-immutable/ , gdzie musisz zbudować i zainicjować, aby wyzerować wektor o określonym rozmiarze. Oto dwie różne implementacje i porównanie prędkości.
Bez listy inicjującej członków , uzyskanie AC kosztowało mnie około 212 ms .
Teraz przy użyciu listy inicjującej członka czas uzyskania AC wynosi około 108 ms . W tym prostym przykładzie oczywiste jest, że lista inicjująca członków jest znacznie bardziej wydajna . Cały pomiar pochodzi z czasu pracy z LC.
źródło
Składnia:
Konieczność listy inicjalizacji:
w powyższym programie, gdy konstruktor klasy jest wykonywany, tworzone są Sam_x i Sam_y . Następnie w treści konstruktora definiowane są te zmienne danych elementu.
Przypadków użycia:
W C zmienne należy zdefiniować podczas tworzenia. w ten sam sposób w C ++ musimy zainicjować zmienną Const i Reference podczas tworzenia obiektu za pomocą listy Inicjalizacja. jeśli wykonamy inicjalizację po utworzeniu obiektu (wewnątrz ciała konstruktora), otrzymamy błąd czasu kompilacji.
Obiekty członkowskie klasy Sample1 (podstawowej), które nie mają domyślnego konstruktora
Podczas tworzenia obiektu dla klasy pochodnej, która wewnętrznie wywoła konstruktor klasy pochodnej i wywołuje konstruktor klasy bazowej (domyślnie). jeśli klasa podstawowa nie ma domyślnego konstruktora, użytkownik otrzyma błąd czasu kompilacji. Aby tego uniknąć, musimy mieć jedno z nich
Nazwa parametru konstruktora klasy i element danych klasy są takie same:
Jak wszyscy wiemy, zmienna lokalna ma najwyższy priorytet niż zmienna globalna, jeśli obie zmienne mają tę samą nazwę. W takim przypadku program bierze pod uwagę wartość „i” {zmienną zarówno po lewej, jak i po prawej stronie. tj .: i = i} jako zmienna lokalna w konstruktorze Sample3 () i zmienna elementu klasy (i) została zastąpiona. Aby tego uniknąć, musimy użyć jednego z nich
źródło
Jak wyjaśniono w Wytycznych podstawowych C ++ C.49: Preferowanie inicjalizacji zamiast przypisania w konstruktorach zapobiega niepotrzebnym wywołaniom domyślnych konstruktorów.
źródło