inheritance


C++ bez cholesterolu: Programowanie hierarchiczne w C++: Dziedziczenie 4.3 Dziedziczenie Konsekwencje dziedziczenia Każdy język obiektowy musi posiadać narzędzia umożliwiające wyprowadzanie innych typów na bazie już istniejących, a przynajmniej możliwości określania jakichkolwiek związków hierarchicznych między typami (w najgorszym wypadku - możliwości przekazywania sobie obiektom nawzajem rozkazów wykonania operacji, ale może nie komplikujmy aż tak - w C++ zdecydowano się na najprostsze rozwiązanie). Stosowanie tych narzędzi nie determinuje jeszcze programowania obiektowego co prawda, ale przecież C++ nie jest językiem służącym do programowania obiektowego (jak Smalltalk), tylko do programowania. Co prawda konsekwencją takiego podejścia jest konieczność wielokrotnie jawnego określania, że rzeczywiście jakiejś - często ponoszącej koszty dodatkowe - właściwości rzeczywiście chce się używać. Jednak w tym przypadku jeszcze nic takiego się nie dzieje. Dziedziczenie powoduje bowiem tylko inny (często dzięki temu skrócony) zapis definicji typów, możliwość częściowego definiowania operacji i tak zwane "składanie z klocków". Typem bazowym dla innych typów oczywiście może być tylko struktura (nie unia!). Nie jest to jednak - jak się później przekonamy - takie znów bolesne ograniczenie. Aby wyprowadzić nowy typ na bazie istniejącego (nazwijmy go `A'), należy zadeklarować: struct B: A { // definicje dodatkowe }; Definicja taka pociąga za sobą odpowiednie konsekwencje. Mianowicie wszystko, co zostało zadeklarowane w strukturze A, znajduje się również w strukturze B. Pamiętajmy jednak, że nie każde A::x staje się w strukturze B dostępne jako B::x (nazywa się to "podleganiem dziedziczeniu"); nie zachowuje się tego mianowicie dla operatorów przypisania i konstruktorów. Te metody są oczywiście dostępne wewnątrz metod struktury B, ale z przedrostkiem `A::'. Powód jest prosty - definicja operatora przypisania i konstruktora kopiującego jest dostosowana do struktury `A' i wcale nie oznacza, że będzie pasowała do struktury `B' (nic nie stoi na przeszkodzie, żeby z nich skorzystać). Dziedziczenie umożliwia przede wszystkim grupowanie typów (nie mówię `klasyfikację', żeby nie zaperzyć ;). Typ B bowiem nie jest w takiej sytuacji typem jakimś zupełnie nowym, lecz typem pochodnym A. A co za tym idzie, wszelkie operacje, które można wykonać na obiekcie typu A, można wykonać na obiekcie typu B. W związku z tym np. wskaźnik na typ `A' może wskazywać na obiekt typu `B', aczkolwiek będzie go traktował jak obiekt typu `A'. Część obiektu typu `B', która jest sama z siebie obiektem typu `A' nazywamy PODOBIEKTEM. Przedstawię jeszcze - trochę dla ciekawostki - jak ominąć niemożliwość dziedziczenia po typach ścisłych i innych, które nie mogą być dziedziczone (enumy i unie): struct Int { int in; operator int() { return in; } Int() {} Int( int i ): in( i ) { } }; Teraz obiekt typu `Int' można śmiało przekazywać do wszystkich funkcji, przyjmujących argument typu `int' (z wbudowanymi operatorami włącznie). Niestety nie powoduje to bynajmniej, że typ `Int' jest pochodną `int' - to jest tylko takie małe "oszustwo". W większości przypadków działa jak trzeba, niestety nie zawsze - pewne właściwości C++ wymagają, aby podawać wartości statyczne typów ścisłych (a *nie* wartości konwertowalne do ścisłych, bo takowe nie mogą być konwertowane na wartości statyczne!). Nie można np. stałymi tego typu oznaczać wymiaru tablic "surowych" (tych zwykłych). Rzutowanie statyczne Konsekwencją grupowania typów jest to, że wskaźnik lub referencja do typu podstawowego może przyjmować również obiekty typów dla niego pochodnych. Tak więc na przykład: struct D: B { ... }; ... B b; D d; B* p = &b; // wiadomo ... p = &d; // obiekt `d' jest traktowany jak obiekt struktury B Poprzez wskaźnik dla struktury bazowej oczywiście można uzyskiwać dostęp tylko do tych składowych obiektu, które są zadeklarowane w strukturze bazowej. Jednak jeśli mamy obiekt struktury pochodnej przypisany do wskaźnika na strukturę bazową, to można go przekonwertować na obiekt struktury pochodnej, używając poznanego już operatora static_cast: D* pd = static_cast<D*>( p ); Operator ten może być użyty w sytuacji, kiedy konwersja w odwrotnym kierunku może być wykonana niejawnie. Jednak taka konwersja - w odróżnieniu od dynamic_cast - wykonuje się podczas kompilacji i nie dokonuje żadnego sprawdzenia. Można więc go użyć tylko wtedy, kiedy się jest absolutnie pewnym, że dany wskaźnik rzeczywiście wskazuje na obiekt takiego typu, na jaki się chce konwertować.

Wyszukiwarka

Podobne podstrony:
inherits
proper inheritance
basics of inheritance
private inheritance
Inheritance
inheritance1
Inherited
InheritableThreadLocal
strange inheritance
inherits
java lab05 inheritance
Inherit the?rth
inherits
inherits
InheritableThreadLocal
inherits
inheritance?B9F437

więcej podobnych podstron