Panel użytkownika
Nazwa użytkownika:
Hasło:
Nie masz jeszcze konta?
Autor: pekfos
Programowanie obiektowe, C++

Konstruktory i destruktory

[lekcja] 4. Konstruktory i destruktory, czyli poprawna inicjalizacja obiektów i sprzątanie po nich

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.
C/C++
class Klasa
{
public:
    Klasa()
    {
    }
   
    Klasa( int a );
};

Klasa::Klasa( int a )
{
}
Konstruktor może przyjmować argumenty, które należy podać podczas tworzenia obiektu
C/C++
Klasa2 a2( 1 );
Konstruktor nieprzyjmujący żadnych argumentów nazywamy konstruktorem domyślnym. Jest on wywoływany, gdy podczas tworzenia obiektu nie podamy żadnych argumentów.
C/C++
Klasa1 a1;

Konstruktor kopiujący

Konstruktor kopiujący jest wywoływany podczas kopiowania obiektu. Taki konstruktor przyjmuje referencję na (stały) obiekt do skopiowania.
C/C++
class Klasa
{
public:
    Klasa( Klasa & );
    //lub
    //Klasa(const Klasa&);
};
C/C++
Klasa a; //k. domyślny
Klasa b( a ); //k. kopiujący

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:
C/C++
class Klasa
{
public:
    Klasa( Klasa && ) { }
};
C/C++
#include <utility>

Klasa k1;
Klasa k2( std::move( k1 ) );

Lista inicjalizacyjna konstruktora

Przeanalizujmy taki kod:
C/C++
class Klasa
{
    const int stala;
    int & ref;
   
public:
    Klasa()
    {
        stala = 1; //przypisanie
        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:
C/C++
class Klasa
{
    const int stala;
    int & ref;
   
public:
    Klasa()
        : /* Po dwukropku jawne wywołania konstruktorów oddzielone przecinkami. */ stala( 1 )
        , ref( stala ) //inicjalizacja
    {
    }
};
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.
C/C++
class Klasa
{
public:
    ~Klasa()
    {
    }
};

Przykład

C/C++
#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
Poprzedni dokument Następny dokument
Statyczne zmienne i metody w klasie Wskaźnik this