4. Konstruktory i destruktory.
Zadania konstruktora
Wywołanie konstruktora powoduje wykonanie następujących zadań:
•
obliczenie rozmiaru obiektu
•
alokacja obiektu w pamięci
•
wyczyszczenie (zerowanie) obszaru pamięci zarezerwowanej dla obiektu (tylko w
niektórych językach)
•
wpisanie do obiektu informacji łączącej go z odpowiadającą mu klasą (połączenie z
metodami klasy)
•
wykonanie kodu klasy bazowej (w niektórych językach nie wymagane)
•
wykonanie kodu wywołanego konstruktora
Z wyjątkiem ostatniego punktu powyższe zadania są wykonywane wewnętrznie i są wszyte
w kompilator lub interpreter języka, lub w niektórych językach stanowią kod klasy
bazowej.
W językach programowania w różny sposób oznacza się konstruktor:
•
w C++, Javie, C# - jest to metoda o nazwie zgodnej z nazwą klasy
•
w Pascalu - metoda której nazwę poprzedzono słowem kluczowym constructor.
W języku C++ wyróżnia się następujące szczególne rodzaje konstruktorów:
–
konstruktor domyślny,
–
zwykły konstruktor,
–
konstruktor kopiujący,
–
konstruktor konwertujący,
Zwykły konstruktor
Konstruktor, który można wywołać, podając co najmniej jeden parametr. Jest to
zwykły konstruktor stworzony przez twórcę klasy. Jego zadeklarowanie w C++ nie
powoduje niejawnego generowania konstruktora domyślnego. Z reguły parametry takiego
zwykłego konstruktora spełniają funkcję inicjalizatorów, które przypisują odpowiednie
wartości wewnętrznym zmiennym tworzonego obiektu, np. (przykład w C++):
class Wektor {
public:
Wektor( double x , double y ) {
this->x = x;
this->y = y;
}
private:
double x;
double y;
};
int main () {
Wektor mojWektor( 3 , 2 );
return 0;
}
Właściwości i ciekawostki
•
W większości języków konstruktor nie może być wirtualny(w efekcie czego nie może
być metodą czysto wirtualną).
•
Konstruktor nie może być statyczny
•
W klasie, gdzie zadeklarowany jest konstruktor kopiujący, powinien być
zadeklarowany dowolny inny konstruktor (domyślny lub inny), ponieważ nie byłoby
możliwe stworzenie obiektu danej klasy. Aby stworzyć obiekt korzystając z
konstruktora kopiującego, należałoby posiadać inny egzemplarz obiektu danej klasy,
który nie może być utworzony, ponieważ jego stworzenie również wymagałoby
egzemplarza danej klasy itd.
•
W klasie, gdzie wymagane jest istnienie: konstruktora kopiującego lub destruktora
lub operatora przypisania, wymagane jest najczęściej istnienie wszystkich trzech.
•
Parametr konstruktora kopiującego nie może być przekazywany
przez wartość,
ponieważ powodowałoby to nieskończone wywołanie konstruktorów kopiujących.
Dla potrzeb wywołania konstruktora należałoby wykonać kopię obiektu. Aby
wykonać kopię obiektu należy wywołać jego konstruktor kopiujący, któremu również
należy przekazać obiekt przez wartość, a więc wykonać jego kopię, itd. Błąd ten nie
przejdzie procesu kompilacji, kompilator rozpoznaje taki przypadek i generuje
sygnał błędu. Nie jest możliwe wygenerowanie nieskończonej pętli wywołań,
ponieważ ciąg takich wywołań miałby teoretycznie nieskończoną długość i
spowodowałby zablokowanie kompilatora.
•
Aby uniemożliwić stworzenie obiektu danej klasy należy:
•
zadeklarować wszystkie konstruktory w sekcji prywatnej (konstruktor
kopiujący może ale nie musi spełniać tego warunku)
•
klasa nie może deklarować przyjaźni z klasą ani funkcją
Działanie takie stosuje się, gdy na przykład klasa ma służyć jako zbiór metod i pól
statycznych i nie jest potrzebny jakikolwiek egzemplarz obiektu danej klasy (również
jako klasy bazowej).
Destruktor - w obiektowych językach programowania specjalna metoda, wywoływana
przez program przed usunięciem obiektu i niemal nigdy nie jest wywoływana wprost w
kodzie używającym obiektu. Pod względem funkcjonalnym jest to przeciwieństwo
konstruktora.
Destruktor ma za zadanie wykonać czynności składające się na jego "zniszczenie",
inne niż zwolnienie pamięci zajmowanej przez sam obiekt, przygotowujące obiekt do
fizycznego usunięcia. Po jego wykonaniu obiekt znajduje się w stanie osobliwym i
zazwyczaj nie można już z tym obiektem zrobić nic poza fizycznym usunięciem. Destruktor
zwykle wykonuje takie czynności, jak zamknięcie połączenia z plikiem/gniazdem/potokiem,
odrejestrowanie się z innych obiektów, czasem również zanotowanie faktu usunięcia, a
także usunięcie obiektów podległych, które obiekt utworzył lub zostały mu przydzielone
jako podległe (jeśli jest ich jedynym właścicielem) lub wyrejestrowanie się z jego
użytkowania (jeśli jest to obiekt przezeń współdzielony).
W większości języków programowania (np C++, Object Pascal) destruktor jest
dziedziczony jak każda inna metoda.
Przykładowy destruktor (w składni C++):
class Samochod{
public:
string marka;
//... (pewne dane i metody)
~Samochod() { //destructor
std::cout << "Samochod " << marka << " zostal usuniety.\n";
}
};
Finalizator
W niektórych językach z wbudowanym odśmiecaczem (np. Java i C#) dostępna jest
składnia finalizatora - specjalnej metody wywoływanej, gdy obiekt jest usuwany przy
odśmiecaniu. W przeciwieństwie do destruktora nie wiadomo w którym dokładnie
momencie działania programu to nastąpi.