Wprowadzenie
Niemal zawsze, gdy tworzymy nowy obiekt klasy, chcemy by miał dokładnie określony, poprawny stan, a gdy usuwamy obiekt, chcemy by sam posprzątał po sobie. C++ umożliwia to, przez mechanizm konstruktorów i destruktorów.
Konstruktor
Konstruktor jest automatycznie wywoływany podczas tworzenia obiektu. Można w nim nadać wartości początkowe składowym, zaalokować pamięć, itp. Konstruktor definiuje się jak metodę, z tą różnicą, że nie piszemy typu zwracanego, a nazwa konstruktora musi być identyczna z nazwą klasy.
class Klasa
{
public:
Klasa()
{
}
Klasa( int a );
};
Klasa::Klasa( int a )
{
}
Konstruktor może przyjmować argumenty, które należy podać podczas tworzenia obiektu
Konstruktor nieprzyjmujący żadnych argumentów nazywamy
konstruktorem domyślnym. Jest on wywoływany, gdy podczas tworzenia obiektu nie podamy żadnych argumentów.
Konstruktor kopiujący
Konstruktor kopiujący jest wywoływany podczas kopiowania obiektu. Taki konstruktor przyjmuje referencję na (stały) obiekt do skopiowania.
class Klasa
{
public:
Klasa( Klasa & );
};
Konstruktor przenoszący
Uwaga: Jeśli nie wiesz, czym są referencje prawostronne i z czym się je je, możesz spokojnie pominąć tą ramkę. | Konstruktor przenoszący został wprowadzony przez standard C++11 i ma na celu możliwie jak najszybsze przeniesienie danych z innego obiektu, zostawiając go w stanie nieprawidłowym. Konstruktor przenoszący przyjmuje referencje prawostronną na obiekt do przeniesienia:
class Klasa { public: Klasa( Klasa && ) { } }; #include <utility>
Klasa k1; Klasa k2( std::move( k1 ) ); |
Lista inicjalizacyjna konstruktora
Przeanalizujmy taki kod:
class Klasa
{
const int stala;
int & ref;
public:
Klasa()
{
stala = 1;
ref = stala;
}
};
Ten kod oczywiście się nie kompiluje.
error: uninitialized member 'Klasa::stala' with 'const' type 'const int' [-fpermissive]
error: uninitialized reference member 'Klasa::ref' [-fpermissive]
error: assignment of read-only member 'Klasa::stala'
Niektóre typy zmiennych wymagają, by zmienne były
zainicjalizowane, ponieważ nie można im
przypisać wartości. W momencie wywołania kodu z ciała konstruktora, składowe są już utworzone. Aby prawidłowo nadać im wartości w czasie ich tworzenia, używa się
listy inicjalizacyjnej konstruktora:
class Klasa
{
const int stala;
int & ref;
public:
Klasa()
: stala( 1 )
, ref( stala )
{
}
};
Taki zapis ma wiele zalet. Jest szybki, czytelny i pozwala nadawać wartości stałym.
Zmienne na liście inicjalizacyjnej konstruktora są inicjalizowane w kolejności, z jaką są definiowane w klasie. Dzieje się tak, ponieważ zmienne muszą być niszczone w odwrotnej kolejności. Gdyby kolejność tworzenia zależała od kolejności na liście inicjalizacyjnej, trzeba byłoby zapisać kolejność tworzenia, ponieważ każdy konstruktor może mieć inaczej zapisaną listę inicjalizacyjną. |
Destruktor
Destruktor jest wywoływany automatycznie podczas niszczenia obiektów. Nie przyjmuje żadnych argumentów. W przeciwieństwie do konstruktorów, w klasie może być tylko jeden destruktor.
class Klasa
{
public:
~Klasa()
{
}
};
Przykład
#include <iostream>
class Test
{
public:
Test()
{
std::cout << "Konstruktor" << std::endl;
}
Test( const Test & )
{
std::cout << "Konstruktor kopiujacy" << std::endl;
}
~Test()
{
std::cout << "Destruktor" << std::endl;
}
};
int main()
{
{
Test t;
}
std::cout << "---" << std::endl;
{
Test t1;
Test t2 = t1;
}
}
Przykładowe standardowe wyjście
Konstruktor
Destruktor
---
Konstruktor
Konstruktor kopiujacy
Destruktor
Destruktor