Dlaczego konstruktory nie są dziedziczone?

33

Jestem zdezorientowany, jakie mogą być problemy, jeśli konstruktor zostanie odziedziczony z klasy podstawowej. Cpp Primer Plus mówi:

Konstruktory różnią się od innych metod klasowych tym, że tworzą nowe obiekty, podczas gdy inne metody są wywoływane przez istniejące obiekty . Jest to jeden z powodów, dla których konstruktory nie są dziedziczone . Dziedziczenie oznacza, że ​​obiekt pochodny może korzystać z metody klasy podstawowej, ale w przypadku konstruktorów obiekt nie istnieje, dopóki konstruktor nie wykona swojej pracy.

Rozumiem, że konstruktor jest wywoływany przed zakończeniem budowy obiektu.

Jak może to prowadzić do problemów, jeśli klasa potomna dziedziczy ( przez dziedziczenie mam na myśli, że klasa potomna jest w stanie przesłonić metodę klasy nadrzędnej itp. Nie tylko dostęp do metody klasy nadrzędnej ), konstruktor nadrzędny?

Rozumiem, że nie ma potrzeby jawnego wywoływania konstruktora z poziomu kodu [nie jestem tego jeszcze świadomy.] Z wyjątkiem tworzenia obiektów. Nawet wtedy możesz to zrobić za pomocą mechanizmu wywołującego nadrzędnego konstruktora [In cpp, using ::or using member initialiser list, In java using super]. W Javie jest wymuszenie, aby wywołać go w 1. linii, rozumiem, że jest to zapewnienie, aby najpierw utworzyć obiekt nadrzędny, a następnie kontynuować budowę obiektu podrzędnego.

Może to zastąpić . Ale nie mogę wymyślić sytuacji, w których może to stanowić problem. Jeśli dziecko odziedziczy konstruktora nadrzędnego, co może pójść nie tak?

Czy to po to, aby uniknąć dziedziczenia niepotrzebnych funkcji. Czy jest w tym coś więcej?

Suvarna Pattayil
źródło
Nie do końca z C ++ 11, ale myślę, że jest w nim jakaś dziedziczenie konstruktora.
yannis
3
Pamiętaj, że cokolwiek robisz w konstruktorach, musisz uważać na niszczyciele.
Manoj R
2
Myślę, że musisz zdefiniować, co oznacza „dziedziczenie konstruktora”. Wszystkie odpowiedzi wydają się mieć różne wersje tego, co według nich oznacza „dziedziczenie konstruktora”. Nie sądzę, by któryś z nich pasował do mojej początkowej interpretacji. Opis „w szarym polu” z góry „Dziedziczenie oznacza, że ​​obiekt pochodny może korzystać z metody klasy podstawowej” implikuje dziedziczenie konstruktorów. Obiekt pochodny z pewnością może i korzysta z metod konstruktora klasy podstawowej, ZAWSZE.
Dunk
1
Dzięki za wyjaśnienie dziedziczenia konstruktora, teraz możesz uzyskać odpowiedzi.
Dunk

Odpowiedzi:

41

W C ++ nie może być właściwego dziedziczenia konstruktorów, ponieważ konstruktor klasy pochodnej musi wykonywać dodatkowe działania, o których konstruktor klasy podstawowej nie musi i nie wie. Te dodatkowe działania to inicjalizacja elementów danych klasy pochodnej (aw typowej implementacji również ustawienie vpointer w celu odniesienia do vtable klas pochodnych).

Kiedy tworzona jest klasa, zawsze musi się zdarzyć kilka rzeczy: konstruktory klasy podstawowej (jeśli istnieją) i członkowie bezpośredni muszą zostać wywołani, a jeśli są jakieś funkcje wirtualne, vpointer musi być ustawiony poprawnie . Jeśli nie zapewniają konstruktora dla klasy, wówczas kompilator będzie stworzyć taki, który wykonuje wymagane czynności i nic więcej. Jeśli podasz konstruktor, ale nie wykonasz niektórych wymaganych działań (na przykład inicjalizacji niektórych elementów), kompilator automatycznie doda brakujące akcje do twojego konstruktora. W ten sposób kompilator zapewnia, że ​​każda klasa ma co najmniej jednego konstruktora i że każdy konstruktor w pełni inicjuje obiekty, które tworzy.

W C ++ 11 wprowadzono formę „dziedziczenia konstruktorów”, w której można poinstruować kompilator, aby wygenerował dla ciebie zestaw konstruktorów, które przyjmują te same argumenty co konstruktory z klasy podstawowej i które po prostu przekazują te argumenty do klasa podstawowa.
Chociaż oficjalnie nazywa się to dziedziczeniem, tak nie jest, ponieważ nadal istnieje funkcja specyficzna dla klasy pochodnej. Teraz jest po prostu generowany przez kompilator, a nie jawnie napisany przez ciebie.

Ta funkcja działa w następujący sposób:

struct Base {
    Base(int a) : i(a) {}
    int i;
};

struct Derived : Base {
    Derived(int a, std::string s) : Base(a), m(s) {}

    using Base::Base; // Inherit Base's constructors.
    // Equivalent to:
    //Derived(int a) : Base(a), m() {}

    std::string m;
};

Derivedma teraz dwa konstruktory (nie licząc konstruktorów kopiowania / przenoszenia). Jeden, który przyjmuje liczbę całkowitą i ciąg znaków oraz taki, który przyjmuje tylko liczbę całkowitą.

Bart van Ingen Schenau
źródło
Zakłada się, że klasa potomna nie będzie miała własnego konstruktora? a odziedziczony konstruktor jest oczywiście nieświadomy elementów potomnych i inicjacji, które należy wykonać
Suvarna Pattayil
C ++ jest zdefiniowany w taki sposób, że niemożliwe jest, aby klasa nie miała własnego konstruktora (ów). Dlatego nie uważam „dziedziczenia konstruktora” za dziedziczenie. To tylko sposób na uniknięcie pisania żmudnych bojlerów.
Bart van Ingen Schenau
2
Myślę, że zamieszanie wynika z faktu, że nawet pusty konstruktor działa za kulisami. Konstruktor naprawdę składa się z dwóch części: prac wewnętrznych i części, którą piszesz. Ponieważ nie są dobrze rozdzielone, konstruktory nie są dziedziczone.
Sarien
1
Proszę rozważyć wskazanie, skąd m()pochodzi i jak zmieniłby się, gdyby na przykład jego typ int.
Deduplicator,
Czy można to zrobić w sposób using Base::Basedorozumiany? Miałoby to poważne konsekwencje, gdybym zapomniał tę linię w klasie pochodnej i muszę odziedziczyć konstruktor we wszystkich klasach pochodnych
Post Self
7

Nie jest jasne, co rozumiesz przez „dziedziczenie konstruktora nadrzędnego”. Używasz słowa zastępującego , co sugeruje, że możesz myśleć o konstruktorach, które zachowują się jak polimorficzne funkcje wirtualne . Celowo nie używam terminu „wirtualne konstruktory”, ponieważ jest to powszechna nazwa wzorca kodu, w którym faktycznie potrzebujesz już istniejącej instancji obiektu, aby utworzyć inną.

Konstruktory polimorficzne są mało przydatne poza wzorcem „wirtualnego konstruktora” i trudno jest wymyślić konkretny scenariusz, w którym można by użyć rzeczywistego konstruktora polimorficznego. Bardzo wymyślony przykład, który w żaden sposób nie jest nawet zdalnie ważny w C ++ :

struct Base {
  virtual Base(unsigned p1, unsigned p2) {...}
};

struct Derived: public Base {
  Derived(unsigned p1, unsigned p2) : Base(p1, p2) override {...}
};

int main(void) {
  unsigned p1 = 0;
  unsigned p2 = 42;
  Derived *p_d1 = new Base(p1, p2); // This might call Derived(unsigned, unsigned).
  Derived *p_d2 = nullptr;
  p_d2 = new Base(p1, p2); // This might call Derived(unsigned, unsigned) too.
}

W tym przypadku wywoływany konstruktor zależy od konkretnego typu tworzonej lub przypisywanej zmiennej. Wykrywanie jest skomplikowane podczas analizowania / generowania kodu i nie ma zastosowania: znasz konkretny typ, który konstruujesz i napisałeś konkretnego konstruktora dla klasy pochodnej. Poniższy prawidłowy kod C ++ robi dokładnie to samo, jest nieco krótszy i jest bardziej wyraźny w tym, co robi:

struct Base {
  Base(unsigned p1, unsigned p2) {...}
};

struct Derived: public Base {
  Derived(unsigned p1, unsigned p2) : Base(p1, p2) {...}
};

int main(void) {
  unsigned p1 = 0;
  unsigned p2 = 42;
  Derived *p_d1 = new Derived(p1, p2); 
  Derived *p_d2 = nullptr;
  p_d2 = new Derived(p1, p2);
}


Drugą interpretacją lub może dodatkowym pytaniem jest - co, jeśli konstruktory klasy Base byłyby automatycznie obecne we wszystkich klasach pochodnych, chyba że zostały wyraźnie ukryte.

Jeśli dziecko odziedziczy konstruktora nadrzędnego, co może pójść nie tak?

Będziesz musiał napisać dodatkowy kod, aby ukryć konstruktor nadrzędny, którego nie można użyć do zbudowania klasy pochodnej. Może się to zdarzyć, gdy klasa pochodna specjalizuje klasę podstawową w taki sposób, że niektóre parametry stają się nieistotne.

Typowym przykładem są prostokąty i kwadraty (zauważ, że kwadraty i prostokąty zasadniczo nie są zastępowalne przez Liskov, więc nie jest to bardzo dobry projekt, ale uwypukla problem).

struct Rectangle {
  Rectangle(unsigned width, unsigned height) {...}
};

struct Square : public Rectangle {
  explicit Square(unsigned side) : Rectangle(side, side) {...}
};

Jeśli Square odziedziczy konstruktor o dwóch wartościach Rectangle, możesz konstruować kwadraty o innej wysokości i szerokości ... To logicznie źle, więc chcesz ukryć tego konstruktora.

Joris Timmermans
źródło
3

Dlaczego konstruktory nie są dziedziczone: odpowiedź jest zaskakująco prosta: konstruktor klasy podstawowej „buduje” klasę podstawową, a konstruktor klasy dziedziczonej „buduje” klasę odziedziczoną. Gdyby dziedziczona klasa odziedziczyła konstruktor, konstruktor spróbowałby zbudować obiekt klasy bazowej typu i nie byłby w stanie „zbudować” obiektu klasy odziedziczonej.

Który rodzaj pokonuje cel dziedziczenia klasy.

Pieter B.
źródło
3

Najbardziej oczywistym problemem zezwalaniem klasie pochodnej na przesłonięcie konstruktora klasy bazowej jest to, że twórca klasy pochodnej jest teraz odpowiedzialny za umiejętność konstruowania swojej klasy (klas) bazowej. Co dzieje się, gdy klasa pochodna nie konstruuje poprawnie klasy podstawowej?

Ponadto zasada Liskowa-podstawiania nie będzie już obowiązywać, ponieważ nie możesz już liczyć na to, że twoja kolekcja obiektów klasy podstawowej będzie ze sobą zgodna, ponieważ nie ma gwarancji, że klasa podstawowa została zbudowana poprawnie lub kompatybilnie z innymi typami pochodnymi.

Jest to jeszcze bardziej skomplikowane, gdy dodawany jest więcej niż 1 poziom dziedziczenia. Teraz twoja klasa pochodna musi wiedzieć, jak zbudować wszystkie klasy podstawowe w łańcuchu.

Co się wtedy stanie, jeśli dodasz nową klasę podstawową na szczycie hierarchii dziedziczenia? Będziesz musiał zaktualizować wszystkie konstruktory klas pochodnych.

Maczać
źródło
2

Konstruktory zasadniczo różnią się od innych metod:

  1. Są generowane, jeśli ich nie napiszesz.
  2. Wszystkie konstruktory klasy bazowej są wywoływane niejawnie, nawet jeśli nie zrobisz tego ręcznie
  3. Nie wywołujesz ich wprost, ale poprzez tworzenie obiektów.

Dlaczego więc nie są dziedziczone? Prosta odpowiedź: ponieważ zawsze występuje nadpisanie, generowane lub zapisywane ręcznie.

Dlaczego każda klasa potrzebuje konstruktora? To skomplikowane pytanie i uważam, że odpowiedź zależy od kompilatora. Istnieje coś takiego jak „trywialny” konstruktor, dla którego kompilator nie nakazuje, aby się go nazywał. Myślę, że jest to najbliższe temu, co rozumiesz przez dziedziczenie, ale z trzech wyżej wymienionych powodów uważam, że porównywanie konstruktorów z normalnymi metodami nie jest tak naprawdę przydatne. :)

Sarien
źródło
1

Każda klasa potrzebuje konstruktora, nawet domyślnego.
C ++ stworzy dla ciebie domyślne konstruktory, chyba że stworzysz specjalistycznego konstruktora.
W przypadku, gdy twoja klasa podstawowa używa wyspecjalizowanego konstruktora, będziesz musiał napisać wyspecjalizowanego konstruktora w klasie pochodnej, nawet jeśli oba są takie same i powiązać je z powrotem.
C ++ 11 pozwala uniknąć powielania kodu w konstruktorach, używając :

Class A : public B {
using B:B;
....
  1. Zestaw dziedziczących konstruktorów składa się z

    • Wszystkie konstruktory inne niż szablony klasy podstawowej (po pominięciu parametrów elipsy, jeśli istnieją) (od C ++ 14)
    • Dla każdego konstruktora z domyślnymi argumentami lub parametrem elipsy wszystkie podpisy konstruktora utworzone przez upuszczenie elipsy i pominięcie domyślnych argumentów na końcach argumentów, list po kolei
    • Wszystkie szablony konstruktorów klasy podstawowej (po pominięciu parametrów elipsy, jeśli istnieją) (od C ++ 14)
    • Dla każdego szablonu konstruktora z domyślnymi argumentami lub wielokropkiem wszystkie podpisy konstruktora, które są tworzone przez upuszczenie wielokropka i pominięcie domyślnych argumentów na końcach argumentów, list po kolei
  2. Wszystkie odziedziczone konstruktory, które nie są domyślnym konstruktorem ani konstruktorem kopiuj / przenieś i których podpisy nie są zgodne z konstruktorami zdefiniowanymi przez użytkownika w klasie pochodnej, są domyślnie deklarowane w klasie pochodnej. Domyślne parametry nie są dziedziczone

MeduZa
źródło
0

Możesz użyć:

MyClass() : Base()

Pytasz, dlaczego musisz to zrobić?

Podklasa może mieć dodatkowe właściwości, które mogą wymagać zainicjowania w konstruktorze, lub może zainicjować zmienne klasy podstawowej w inny sposób.

Jak inaczej utworzyłbyś obiekt podtypu?

Tom Tom
źródło
Dzięki. W rzeczywistości próbuję dowiedzieć się, dlaczego konstruktor nie jest dziedziczony w klasie potomnej, podobnie jak inne metody klasy nadrzędnej.
Suvarna Pattayil