programowanieProgramista C++/Backend

Czym są konstruktory delegujące w C++, kiedy i dlaczego ich używać?

Zdaj rozmowy kwalifikacyjne z asystentem AI Hintsage

Odpowiedź.

Historia pytania

Przed standardem C++11, przy konieczności napisania kilku konstruktorów z różnymi parametrami, trzeba było duplikować kod inicjalizacji w każdym z nich, ponieważ nie było możliwości wywołania innego konstruktora wewnątrz konstruktorów klasy.

Problem

Niemozliwość użycia już istniejącej logiki inicjalizacji w innych konstruktorach prowadziła do duplikacji kodu i błędów przy zmianie logiki inicjalizacji: aktualizując jeden konstruktor, łatwo było zapomnieć o pozostałych.

Rozwiązanie

Wraz z wprowadzeniem C++11 pojawił się mechanizm "konstruktory delegujące", który pozwala jednemu konstruktorowi klasy wywołać inny konstruktor tej samej klasy za pomocą składni listy inicjalizacji.

Przykład kodu:

class Widget { public: Widget() : Widget(0, "default") {} Widget(int n) : Widget(n, "user") {} Widget(int n, std::string name) : size(n), label(name) {} private: int size; std::string label; };

Teraz cała wspólna logika jest skoncentrowana w jednym "głównym" konstruktorze, a wszystko inne deleguje wywołanie.

Kluczowe cechy:

  • Pozwalają uniknąć duplikacji kodu inicjalizacji
  • Zwiększają niezawodność i utrzymywność
  • Delegacja wewnątrz listy inicjalizacji, a nie w ciele konstruktora

Pytania z haczykiem.

Czy można delegować wywołanie do innego konstruktora przez wywołanie funkcji wewnątrz ciała konstruktora?

Nie. Wywołanie konstruktora innego konstruktora jest możliwe tylko przez listę inicjalizacji, a nie wewnątrz ciała konstruktora. Jeśli wywołasz funkcję z ciała konstruktora, człony obiektu będą już zainicjalizowane.

Co się stanie, jeśli stworzysz "pierścień" delegacji między konstruktorami?

Cykliczna delegacja (na przykład A deleguje do B, a B – do A) prowadzi do błędu kompilacji C++: nie może być nieskończonej rekurencji między konstruktorami.

Czy można delegować wywołanie konstruktora klasy bazowej za pomocą konstruktora delegującego klasy pochodnej?

Nie. Konstruktory delegujące działają tylko w obrębie jednej klasy. Do wywołania konstruktora bazowego używa się osobnej składni:

class Base { public: Base(int) {} }; class Derived : public Base { public: Derived() : Base(42) {} };

Typowe błędy i antywzorce

  • Naruszenie czytelności przy nadmiarze konstruktorów delegujących
  • Cykliczna delegacja — błąd kompilacji
  • Próby delegowania wywołania do konstruktora klasy bazowej przez konstruktor delegujący

Przykład z życia

Negatywny przypadek

Trzy konstruktory, każdy wyraźnie inicjalizuje pola, logika nie jest zsynchronizowana — zmiany wprowadzono tylko w jednym konstruktorze, pozostałe dwa są niepoprawne.

Zalety:

  • Łatwe w pisaniu

Wady:

  • Wysokie ryzyko rozbieżności logiki między konstruktorami

Pozytywny przypadek

Jeden główny konstruktor, pozostałe mu delegują, cała logika inicjalizacji jest scentralizowana.

Zalety:

  • Łatwe w utrzymywaniu, wykluczona rozbieżność logiki
  • Minimalizacja duplikacji kodu

Wady:

  • Należy przyzwyczaić się do składni listy inicjalizacji