C++/Konstruktor i destruktor
Z Wikibooks, biblioteki wolnych podręczników.
Spis treści |
[edytuj] Teoria
[edytuj] Wstęp
Pisząc klasy każdy kiedyś dotrze do momentu, w którym będzie odczuwał potrzebę napisania funkcji wykonującej jakieś niezbędne instrukcje na początku lub na końcu istnienia obiektu. W takim momencie programista powinien sięgnąć po dwa niezwykle przydatne narzędzia: konstruktory i destruktory.
[edytuj] Konstruktor
Konstruktor jest to funkcja w klasie, wywoływana w trakcie tworzenia każdej instancji. Funkcja może stać się konstruktorem gdy spełni poniższe warunki
- Ma identyczną nazwę jak nazwa klasy
- Nie zwraca żadnej wartości (nawet void)
- Jest ustawiona w przestrzeni publicznej klasy (sekcja public:)
Należy dodać że każda klasa ma swój konstruktor. Nawet jeżeli nie zadeklarujemy go jawnie zrobi to za nas kompilator (stworzy wtedy konstruktor bezparametrowy).
Mamy na przykład klasę Miesiac. Chcielibyśmy, aby każdy obiekt tej klasy tuż po utworzeniu wygenerował tablicę z nazwami dni tygodnia w zależności od miesiąca i roku. A może dało by się to zrobić w trakcie tworzenia klasy?
Przyjrzyj się poniższej klasie, oraz funkcji konstruktora:
class Miesiac { public: int dni[31]; int liczbaDni; char nazwa[20]; Miesiac();//deklaracja konstruktora }; Miesiac::Miesiac()//definicja konstruktora { /* instrukcje tworzące */ }
Konstruktor może też przyjmować argumenty. Jak?
To zależy od sposobu w jaki tworzymy obiekt:
- jako obiekt
MojaKlasa obiekt(argumenty);
- jako wskaźnik do obiektu:
MojaKlasa* wsk = new MojaKlasa(argumenty);
Teraz powyższa klasa miesiąca może być stworzona z uwzględnieniem numeru miesiąca i roku:
class Miesiac { public: int dni[31]; int liczbaDni; char nazwa[20]; Miesiac(int numer,int rok); }; Miesiac::Miesiac(int numer,int rok) { /* instrukcje tworzące */ }
Aby utworzyć nowy obiekt tej klasy trzeba będzie napisać:
Miesiac styczen2000(1,2000);
lub jako wskaźnik do obiektu:
Miesiac* styczen2000 = new Miesiac(1,2000);
otrzymawszy w ten sposób kalendarz na styczeń.
W przypadku dziedziczenia zawsze najpierw jest wykonywany konstruktor nadklasy. Jeżeli nie podamy jawnie, który konstruktor nadklasy ma się wykonać, zostanie wykonany konstruktor bezparametrowy (jeżeli taki nie istnieje kompilator zwróci informacje o błędzie). Możemy jawnie wywołać inny konstruktor np. w taki sposób:
class Miesiac { public: int numer; int rok; Miesiac(int numer,int rok) { this->numer=numer; this->rok=rok; } }; class Grudzien : public Miesiac { public: Grudzien(int rok) : Miesiac(12, rok) //tutaj wywołujemy konstruktor klasy pierwotnej {} };
Najczęstszą funkcją konstruktora jest inicjalizacja obiektu, oraz alokacja pamięci (np. poprzez stworzenie potrzebnych obiektów).
[edytuj] Konstruktor kopiujący
Konstruktor kopiujący to konstruktor spełniający specyficzne zadanie. Mianowicie może on zostać wywoływany przez kompilator niejawnie jeżeli zachodzi potrzeba stworzenia drugiej instancji obiektu (np. podczas przekazywania obiektu do funkcji przez wartość).
Jeżeli nie zaimplementujemy konstruktora kopiującego, kompilator zrobi to automatycznie. Konstruktor taki będzie po prostu tworzył drugą instancję wszystkich pól obiektu. Możemy go jawnie wywołać np. tak:
Miesiac miesiac(12,2005); Miesiac kopia(miesiac); //tu zostanie wywołany konstruktor kopiujący /* obiekt kopia będzie miał taką samą zawartość jak obiekt miesiąc */
Jeżeli chcemy sami zaimplementować konstruktor kopiujący musimy zadeklarować go jako konstruktor o jednym parametrze będącym referencją na obiekt tej samej klasy.
class Miesiac { public: int numer; int rok; Miesiac(const Miesiac &miesiac) { numer=miesiac.numer; rok=miesiac.rok; } };
[edytuj] Destruktor
Destruktor jest natomiast funkcją, którą wykonuje się w celu zwolnienia pamięci; następuje niszczenie obiektu danej klasy.
Zasady "przemiany" zwykłej funkcji do destruktora, są podobne do tych tyczących się konstruktora. Jedyna zmiana tyczy się nazwy funkcji: Musi się ona zaczynać od znaku tyldy - ~.
class MojaKlasa { MojaKlasa();//to oczywiście jest konstruktor ~MojaKlasa();//a to - destruktor };
Najczęstszą funkcją destruktora jest zwolnienie pamięci (zwykle poprzez zniszczenie wszystkich pól używanych przez ten obiekt).
| Porada Należy pamiętać, że jeżeli zamierzamy implementować dziedziczenie po klasie dla której piszemy destruktor to powinniśmy stworzyć destruktor wirtualny! class MojaKlasa { MojaKlasa(); virtual ~MojaKlasa();//to jest destruktor wirtualny }; Początkujący programiści często o tym zapominają, doprowadzając w ten sposób czasami do tzw. wycieków pamięci. Dobrą praktyką jest tworzenie tylko destruktorów wirtualnych (patrz Funkcje wirtualne). |
[edytuj] Ćwiczenia
[edytuj] Ćwiczenie 1
Napisz definicje konstruktorów do poniższej klasy:
class Vector { public: double x; double y; public: Vector(); Vector(double x, double y); };
Klasa ma reprezentować wektor w przestrzeni dwuwymiarowej, a konstruktory mają realizować inicjalizację tego wektora. Pierwszy konstruktor powinien ustawiać wektor na wartość domyślną (0,0).
[edytuj] Ćwiczenie 2
Dopisz do kodu z poprzedniego ćwiczenia konstruktor kopiujący.
Vector(const Vector &vector);
Po wykonaniu tego ćwiczenia zastanów się, czy napisanie konstruktora kopiującego było konieczne. Jeżeli nie jesteś pewien - napisz program który testuje działanie Twojego konstruktora kopiującego i sprawdź jak program działa bez niego. Wyjaśnij dlaczego konstruktor kopiujący nie jest potrzebny.
[edytuj] Ćwiczenie 3
Poniższa klasa miała implementować dowolnej wielkości tablicę obiektów klasy Vector z poprzednich ćwiczeń. Niestety okazało się że powoduje wycieki pamięci - programista zapomniał o napisaniu destruktora:
class VectorsArray { protected: int size; //ilość wektorów w tablicy public: Vector [] vectors; public: VectorsArray(int size) { this->size = size; vectors = new Vectors[size]; } Vector GetVector(int i) { return Vector[i]; } int GetSize() { return size; } };
Do powyższej klasy dopisz definicję destruktora. Nie zapomnij o dealokacji pamięci!