D/Konstruktor i destruktor

< D

Wstęp

edytuj

Konstruktory i destruktory to specjalne funkcje wywoływane kolejno podczas tworzenia i usuwania obiektu. Muszą one mieć specjalne nazwy i nie zwracają wartości. Dzięki nim można inicjalizować wartości składowych bardziej skomplikowanymi operacjami niż zwykłe przypisanie, jednocześnie mając pewność że nikt nie spróbuje ich wcześniej użyć lub sprawnie posprzątać pamięć podczas usuwania obiektu.

Konstruktor

edytuj

Konstruktor zawsze ma nazwę this i może przyjmować parametry. Klasy mogą mieć wiele konstruktorów (czyli mogą być przeciążone - jak zwykła funkcja). Przykładowa klasa z konstruktorem:

module main;

/* writeln */
import std.stdio;

class licznikInstancji
{
private:
    /* Zmienna statyczna zliczająca instancje */
    static uint instancje = 0;
public:
    /* Konstruktor bez parametrów */
    this()
    {
        writeln("Utworzono instancję nr ", instancje++, "!");
    }
}

int main(string[] args)
{
    /* Utworzenie dwóch instancji */
    auto licznik1 = new licznikInstancji();
    auto licznik2 = new licznikInstancji();

	return 0;
}

Po kompilacji na wyjście standardowe wypisane zostanie to:

Utworzono instancję nr 0!
Utworzono instancję nr 1!

Konstruktor z parametrami

edytuj

Konstruktory z parametrami tworzymy analogicznie do zwykłych funkcji, zaleca się jednak posiadanie konstruktora bezparametrowego, ponieważ nie zawsze ma się bezpośrednią kontrolę nad procesem tworzenia obiektu.

module main;

/* writeln */
import std.stdio;

class Sumator
{
private:
    static uint suma = 0;
public:
    /* Konstruktor z parametrami */
    this(int i)
    {
        writeln("Aktualna suma: ", (suma += i));
    }
}

int main(string[] args)
{
    /* Tablica obiektów */
    Sumator tab[];
    tab ~= new Sumator(5);
    tab ~= new Sumator(6);
    tab ~= new Sumator(7);

	return 0;
}

Stdout:

Aktualna suma: 5
Aktualna suma: 11
Aktualna suma: 18

Jak widać pominąłem tu konstruktor domyślny, jednak problem ten rozwiążemy (ładniej niż moglibyśmy z obecną wiedzą) za chwilę.

This w konstruktorze

edytuj

Konstruktory mogą się na wzajem wywoływać korzystając ze swojej nazwy - this. Poniższy przykład bez zbędnego opisu wyczerpie temat:

module main;

/* writeln */
import std.stdio;

class Sumator
{
private:
    static uint suma = 0;
public:
    /* Konstruktor z parametrami */
    this(int i)
    {
        writeln("Aktualna suma: ", (suma += i));
    }
    /* Konstruktor bez parametrów */
    this()
    {
        this(0);
    }
}

int main(string[] args)
{
    /* Tablica obiektów */
    Sumator tab[];
    tab ~= new Sumator(5);
    tab ~= new Sumator();
    tab ~= new Sumator(7);

	return 0;
}

Stdout:

Aktualna suma: 5
Aktualna suma: 5
Aktualna suma: 12

Jeżeli nie poznałeś jeszcze dziedziczenia możesz pominąć na razie ten temat. (Ale pamiętaj do niego wrócić!) Konstruktory klas pochodnych mogą wykonywać konstruktory klas bazowych wykorzystując funkcję super. Działa ona mniej-więcej jak this, ale wywołuje konstruktor „poziom wyżej”. Prosty przykład:

module main;

/* writeln */
import std.stdio;

class Sumator
{
protected:
    static uint suma = 0;
public:
    /* Konstruktor z parametrami */
    this(int i)
    {
        suma += i;
    }
    /* Konstruktor bez parametrów */
    this()
    {
        this(0);
    }
}

/* Klasa WyswietlajacySumator dziedziczy po klasie Sumator */
class WyswietlajacySumator
    : Sumator
{
public:
    /* Konstruktor z parametrami */
    this(int i)
    {
        /* Wywołanie konstruktora klasy Sumator */
        super(i);
        writeln("Zsumowana wartość: ", suma);
    }
    /* Konstruktor bez parametrów */
    this()
    {
        this(0);
    }
}

int main(string[] args)
{
    /* Tablica obiektów */
    WyswietlajacySumator tab[];
    tab ~= new WyswietlajacySumator(5);
    tab ~= new WyswietlajacySumator();
    tab ~= new WyswietlajacySumator(7);

	return 0;
}

Stdout:

Aktualna suma: 5
Aktualna suma: 5
Aktualna suma: 12

Konstruktor statyczny

edytuj

Konstruktory statyczne, czyli takie poprzedzone słowem static, jak zwykłe metody statyczne mogą operować wyłącznie na statycznych składowych. Wywoływane są zawsze tylko raz, jeszcze przed rozpoczęciem funkcji main.

module main;

/* writeln */
import std.stdio;

class licznikInstancji
{
private:
    /* Zmienna statyczna zliczająca instancje */
    static uint instancje;
public:
    /* Konstruktor bez parametrów */
    this()
    {
        writeln("Utworzono instancję nr ", instancje++, "!");
    }

    /* Konstruktor statyczny */
    static this()
    {
        /* Zliczanie zaczniemy od 1 */
        instancje = 1;
    }
}

int main(string[] args)
{
    /* Utworzenie dwóch instancji */
    auto licznik1 = new licznikInstancji();
    auto licznik2 = new licznikInstancji();

	return 0;
}

Stdout:

Utworzono instancję nr 1!
Utworzono instancję nr 2!

Destruktor

edytuj

Destruktor jest funkcją wywoływaną zawsze bez parametrów w momencie usuwania obiektu.

Drobna dygresja – w D nie zawsze mamy pewność kiedy obiekt jest usuwany, ponieważ Garbage Collector zabierze się za niego dopiero gdy uzna to za stosowne. Możemy wymusić usunięcie po wyjściu z bloku kodu używając szablonu scoped. Inną opcją wywołania destruktora jest użycie object.destroy (o tym jak trochę dalej).

Destruktory zawsze mają nazwę ~this (analogiczną do ~NazwaKlasy z C++) i najczęstszym zastosowaniem jest sprzątanie po instancji w dowolnym tego słowa znaczeniu. Przykład z rozdziału o konstruktorach rozszerzony o destruktor:

module main;

/* writeln */
import std.stdio;

class licznikInstancji
{
private:
    /* Zmienna statyczna zliczająca instancje */
    static uint instancje;
public:
    /* Konstruktor bez parametrów */
    this()
    {
        writeln("Utworzono instancję nr ", instancje++, "!");
    }
    /* Konstruktor statyczny */
    static this()
    {
        /* Zliczanie zaczniemy od 1 */
        instancje = 1;
    }

    /* Podstawowy destruktor */
    ~this()
    {
        writeln("Pozostałych instancji: ", --instancje - 1);
    }
}

int main(string[] args)
{
    /* Utworzenie dwóch instancji */
    auto licznik1 = new licznikInstancji(); //Instancja 1
    auto licznik2 = new licznikInstancji(); //Instancja 2
    destroy(licznik1);                      //Pozostała 1
    licznik1 = new licznikInstancji();      //Nowa instancja 2

	return 0;
                                            //Pozostała 1
                                            //Pozostało 0
}

Stdout:

Utworzono instancję nr 1!
Utworzono instancję nr 2!
Pozostałych instancji: 1
Utworzono instancję nr 2!
Pozostałych instancji: 1
Pozostałych instancji: 0

Destruktor statyczny

edytuj

Destruktory statyczne wywoływane są po zakończeniu wątku w kolejności odwrotnej do kolejności wywoływania statycznych konstruktorów. (Utożsamiając klasy z typami nawiasów, możemy wyobrazić sobie przebieg programu jako taki ciąg [{}], czyli zgodne pary konstruktor–destruktor nawzajem się domykają.) Jak normalne destruktory nie mogą przyjmować parametrów, ale mamy względną pewność co do momentu wykonania.

module main;

/* writeln */
import std.stdio;

class licznikInstancji
{
private:
    /* Zmienna statyczna zliczająca instancje */
    static uint instancje;
public:
    /* Konstruktor bez parametrów */
    this()
    {
        writeln("Utworzono instancję nr ", instancje++, "!");
    }
    /* Konstruktor statyczny */
    static this()
    {
        /* Zliczanie zaczniemy od 1 */
        instancje = 1;
    }

    /* Podstawowy destruktor */
    ~this()
    {
        writeln("Pozostałych instancji: ", --instancje - 1);
    }
    /* Destruktor statyczny */
    static ~this()
    {
        writeln("Kończenie wykonania z następującą ilością instancji: ", instancje - 1);
    }
}

int main(string[] args)
{
    /* Utworzenie dwóch instancji */
    auto licznik1 = new licznikInstancji(); //Instancja 1
    auto licznik2 = new licznikInstancji(); //Instancja 2
    destroy(licznik1);                      //Pozostała 1
    licznik1 = new licznikInstancji();      //Nowa instancja 2

	return 0;                               //Koniec programu - zostały 2
                                            //Pozostała 1
                                            //Pozostało 0
}

Stdout:

Utworzono instancję nr 1!
Utworzono instancję nr 2!
Pozostałych instancji: 1
Utworzono instancję nr 2!
Kończenie wykonania z następującą ilością instancji: 2
Pozostałych instancji: 1
Pozostałych instancji: 0