C++ od kuchni.pdf

(147 KB) Pobierz
24887929 UNPDF
Jakub Wach,
Michał Pelczar
C++ od kuchni,
czyli rola kompilatora w modelu obiektu języka C++
1.Wprowadzenie
2.Semantyka konstruktorów
3.Semantyka danych
4.Semantyka funkcji
5.Semantyka konstruowania
6.Semantyka destrukcji
7.Zagadnienia semantyki czasu wykonania
1. Wprowadzenie
Języki umożliwiające programowanie metodą OO posiadają bardzo silnie
rozbudowaną funkcjonalność obiektową. Funkcjonalność ta musi zostać w
odpowiedni sposób zaimplementowana. Programista, operując na wyższym poziomie
abstrakcji z reguły nie jest zainteresowany stroną implementacji, co jest poważnym
błędem. Z reguły bowiem kod, jaki powstaje po kompilacji, przedstawia się zupełnie
inaczej niż kod przez nas zamierzony. Może to prowadzić do pomyłek zwłaszcza,
gdy programujemy wykorzystując bardzo zaawansowane elementy składni – im
większy stopień skomplikowania kodu, tym mniejsza pewność, co tak naprawdę się
w nim dzieje. Dodatkowym problemem jest istnienie różnych standardów i
dialektów języka i szerokiego zakresu kompilatorów, na których konfigurację mamy
bardzo mały wpływ i które niezbyt chętnie informują o koncepcyjnych zmianach
dokonywanych w kodzie.
Zagadnienia omówimy na przykładzie języka C++ który podobnie jak większość
stosowanych obecnie, łączy w sobie paradygmat proceduralny i obiektowy.
Zaprezentowany pseudokod języka C++ opiera się na kompilatorze cfront .
2. Konstruktory
2.1.Konstruktory domyślne
Standardy mówią, że konstruktory domyślne generowane są “tam, gdzie to
potrzebne” i gdzie są one nietrywialne , co obejmuje następujące przypadki:
składowy obiekt klasy z konstruktorem domyślnym
class Foo (public: Foo(), .... }
class Bar { public: Foo foo; }
... Bar bar; ...
W tej sytuacji, należy stworzyć konstruktor, który wywoła konstruktor Foo:Foo().
Aby jednak synteza nie spowodowała konfliktów w przypadku wspólnej kompilacji
kilku plików tworzących obiekty bar, konstruktor musi zostać zsyntetyzowany jako
funkcja inline:
inline Bar:Bar() {
foo.Foo::Foo(); *
która zawsze jest łączona statycznie.
Co więcej, linijka ta jest dopisywana we wszystkich istniejących konstruktorach.
}
klasa podstawowa z kontruktorem domyślnym
Sytuacja analogiczna, konstruktor musi zostaje niejawnie wywołany.
klasa z funkcją wirtualną
Należy zainicjalizować wskaźnik vptr .
klasa z wirtualną klasą podstawową
Należy zainicjalizować wskaźniki do wirtualnych klas podstawowych, przykładowo
dla klasy wirtualnej X wskaźnik __vbX.
2.2.Kontruktory kopiujące.
Dla przypomnienia, ogólna postać konstruktora kopiującego jest następująca:
Konstruktor kopiujący X::X(const X& x)
Domyślą metodą kopiowania obiektu jest kopiowanie bit po bicie - i wówczas
konstruktor w ogóle nie musi istnieć.
Jest on natomiast syntetyzowany, jeśli klasa nie ujawnia semantyki kopiowania bit
po bicie:
class Square { char *str; int cnt; }
class Square { String str; int cnt; }
inline Square:Square( const Square* wd) {
str.String::String(wd.str);
cnt = wd.cnt;
Ponieważ, jak widać, niektóre atrybuty muszą zostać zainicjalizowane w szczególny
sposób.
2.3.Optymalizacja NRV
Podstawowa metoda używana przy zwracaniu obiektów jako rezultaty wykonania
funkcji. W miejscu wywołania metody tworzony jest obiekt. Referencja do tego
obiektu jest potajemnie przesyłana do wnętrza funkcji, która pracuje na orginale.
Żadne kopiowanie obiektu nie jest więc potrzebne. Schematycznie jest to
przedstawione poniżej:
}
X bar() {
X xx;
// operacje na xx
return xx;
void bar(X& __result) {
__result.X::X();
// operacje na __result
return;
Zasadniczą wadą tej optymalizacji jest to, że wykonuje się ona milcząco nie mamy
jasności, czy została wykonana. Ponadto, dla skomplikowanych funkcji jest ona
trudna do realizacji.
3. Semantyka danych
3.1.Rozmiar obiektu
Rozważmy następującą hierachę klas:
class X {};
class Y : public virtual X {};
class Z : public virtual X {};
class A : public Y, public Z {};
Występuje tutaj jak widać dziedziczenie wielokrotne.
Wielkości klas mogą, w zależności od kompilatora, być na przykład następujące:
sizeof(X) 1B
sizeof(Y) 8B
sizeof(Z) 8B
sizeof(A) 12B
Klasa pusta nie ma zerowego rozmiaru.
Kompilator wstawia składową typu char, co pozwala przydzielać obiektom
różne adresy w pamięci.
Ponadto, narzut na rozmiar pozostałych klas jest wypadkową czynników:
wsparcie językowe
Wskaźnik do podobiektu wirtualnej klasy podstawowej albo tablicy
zawierającej jego adres lub przesunięcie.
}
}
rozmieszczenie w pamięci
Wyrównanie do pełnej liczby bajtów, co umożliwia efektywniejsze
ładowanie i zapisywanie.
optymalizacja rozpoznanych przez kompilator przypadków szczególnych
W praktyce, pusta wirtualna klasa podstawowa jest używana w C++ jako wirtualny
interfejs.
Nowe kompilatory traktują taką klasę, jakby nakładała się na początek obiektu klasy
pochodnej. Nie zajmuje ona żadnej dodatkowej pamięci. Jest to przykład
ewolucyjnego charakteru modelu obiektu C++. Model dotyczy przypadku ogólnego.
W miarę, jak wykrywane są pewne przypadki szczególne, wprowadza się heurystyki
umożliwiające ich optymalizację
3.2.Organizacja danych w pamięci, konsekwencje na przykładzie procedury
przypisania
Dziedziczenie bez polimorfizmu
Obiekt klasy pochodnej jest w istocie konkatenacją jej składowych ze składowymi
klas podstawowych (standard nie określa kolejności części klasy pochodnej i
podstawowej).
Dane znajdują się w jednym miejscu, ale korzystać z nich i operować na nich mogą
dwie lub więcej powiązanych ze sobą abstrakcji. Dlatego też dziedziczenie nie
dokłada do reprezentacji żadnego narzutu na pamięć oraz czas dostępu .
Dziedziczenie z polimorfizmem
Aspekty implementacji dziedziczenia z polimorfizmem są następujące:
Wprowadzenie tablicy wirtualnej zawierającej adresy wszystkich funkcji
wirtualnych zadeklarowanych w klasie. Jej rozmiar jest proporcjonalny do liczby
zadeklarowanych funkcji oraz miejsca do rozpoznawania typów w czasie
wykonania.
Wprowadzenie do każdego obiektu wskaźnika vptr . Pozwala on efektywnie
sięgać do tablicy wirtualnej.
Rozszerzenie konstruktora o inicjowanie wskaźnika vptr obiektu adresem tablicy
wirtualnej jego klasy. Możliwe są tutaj optymalizacje.
Rozszerzenie destruktora polegające na zmianie wskaźnika vptr na adres
związanej z klasą tablicy wirtualnej. Optymalizacja może wyeliminować część z
tych przypisań.
Zgłoś jeśli naruszono regulamin