Klasy w C++11

Nowe elementy klas w C++11 umożliwiają:

  • Jawne definiowanie operacji specjalnych jako domyślne

  • Jawne blokowanie domyślnych operacji specjalnych

  • Blokowanie dziedziczenia po danej klasie

  • Blokowanie nadpisywania implementacji metod w klasach pochodnych

  • Upewnienie się na etapie kompilacji, że nadpisywana metoda istnieje w klasie bazowej

  • Użycie przez konstruktor innych konstruktorów klasy

  • Inicjalizowanie składowych klasy w miejscu deklaracji

Specjalne funkcje składowe klas

Specjalne funkcje składowe klas w C++11:

  • Konstruktor domyślny

  • Destruktor

  • Operacje kopiowania - konstruktor kopiujący i kopiujący operator=

  • Operacje przenoszenia - konstruktor przenoszący i przenoszący operator=

Wszystkie specjalne funkcje składowe są generowane przez kompilator i posiadają następujące cechy:

  • są publiczne

  • są inline

  • są non-explicit

C++11 daje możliwość jawnego zadeklarowania funkcji specjalnych jako domyślnych lub usunięcia ich z interfejsu klasy.

Domyślne specjalne funkcje składowe - default

Deklaracja default - wymusza na kompilatorze generację domyślnej implementacji dla deklaracji specyfikowanej przez użytkownika (np. generacja domyślnego konstruktora w przypadku, gdy istnieją inne konstruktory przyjmujące parametry)

class Gadget
{
public:
    Gadget(const Gadget&); // copy constructor will prevent
                           // generating implicitly declared
                           // default ctor and move operations

    Gadget() = default;
    Gadget(Gadget&&) noexcept = default;
};

Operacje zadeklarowane jako default są traktowane jako user-declared. W efekcie klasa:

class Any // default copy semantics enabled
{

};

nie jest taka sama jak klasa zaimplementowana w poniższy sposób:

class Any  // default copy semantics deprecated in C++14 (and later probably disabled)
{
    ~Any = default;
};

Usunięte funkcje składowe - delete

Deklaracja delete - usuwa wskazaną funkcję lub funkcję składową z interfejsu klasy. Nie jest generowany kod takiej funkcji, a wywołanie jej, pobranie adresu lub użycie w wyrażeniu z sizeof jest błędem kompilacji.

Usunięcie funkcji umożliwia uniknięcie niejawnej konwersji argumentów wywołania funkcji:

void integral_only(int a)
{
    cout << "integral_only: " << a << endl;
}

void integral_only(double d) = delete;

// ...

integral_only(10); // OK

short s = 3;
integral_only(s); // OK - implicit conversion to short

integral_only(3.0); // error - use of deleted function

Domyślna inicjalizacja nie-statycznych składowych klasy

W C++11 można inicjalizować nie-statyczne składowe klasy bezpośrednio w miejscu ich deklaracji.

Wartość użyta do inicjalizacji będzie przypisana składowej, jeśli wywoływany konstruktor nie nadpisze jej inną wartością.

Operacje kopiowania lub przenoszenia ignorują wartości domyślne

int default_id()
{
    static int id = 0;
    return ++id;
}

class Gadget
{
private:
    int id_ = default_id();
    double price_ = 0.99;
    std::string name_ {"unknown"};
public:
    Gadget() = default;

    Gadget(int id) : id_ {id} {}

    Gadget(int id, double price) : id_{id}, price_ {price} {}

    Gadget(int id, double price, const std::string& name)
        : id_ {id}, price_ {price}, name_ {name}
    {}
};

// ...

Gadget a;            // a.id_ = 1, a.price_ = 0.99, a.name_ = "unknown"

Gadget b = 5;        // a.id_ = 5, a.price_ = 0.99, a.name_ = "unknown"

Gadget c {7, 2.99};  // a.id_ = 7, a.price_ = 2.99, a.name_ = "unknown"

Gadget d {9, 2.99, "item#1"};  // a.id_ = 9, a.price_ = 2.99, a.name_ = "item#1"

Ostrzeżenie

Użycie domyślnej inicjalizacji wewnątrz struktury powoduje, że przestaje ona być agregatem.

Delegowanie konstruktorów

Konstruktor może wywoływać inne konstruktory tej samej klasy.

class Item
{
private:
    int id_;
public:
    Item(int id) : id_ {id} // non-delegating constructor
    {
        // a
    }

    Item() : Item {-1} // delegating constructor
    {
        // b (called after a)
    }

    Item(const std::string& id) : Item {std::stoi(id)} // delegating constructor
    {
        // c (called after a)
    }
};

Ważne

Obiekt jest uznany za poprawnie skonstruowany, gdy pierwszy konstruktor wywołany zakończy się bez wyjątku (=> wywołany będzie destruktor obiektu).

Dziedziczenie konstruktorów

Deklaracja using może być użyta w połączeniu z konstruktorami klasy bazowej. Powoduje to niejawne deklaracje konstruktorów klasy pochodnej, które przyjmują takie same listy paramtrów co konstruktory klasy bazowej. Ich implementacja polegająca na wywołaniu wersji z klasy bazowej jest generowana tylko wtedy, gdy są one rzeczywiście użyte.

class Base
{
public:
    explicit Base(int);
    void do_something(int);
};

class Derived : public Base
{
public:
    using Base::Base; // OK in C++11
                      // implicit declaration of Derived::Derived(int)

    Derived(int, int); // overloaded inherited Base ctor
};

Użycie dziedziczenia konstruktorów w klasach pochodnych, które dodają nowe pola może być ryzykowne:

class Augmented : public Derived
{
public:
    using Derived::Derived;

private:
    std::string name_;
    int value_;
};

Augmented a {10}; // a.name_ == "" - default init
                  // and a.value_ is uninitialized

Kontrola nadpisywania metod wirtualnych - override

Słowo override ma specjalne znaczenie w deklaracji klas i powoduje sprawdzenie na etapie kompilacji, czy nadpisywana metoda jest zadeklarowana w taki sam sposób w klasie bazowej.

class Base
{
public:
    virtual void f();
    virtual void g() const;
    virtual void h(char);
    void k();
};

class Derived1 : public Base
{
public:
    void f(); // overrides Base::f()
    void g(); // doesn't override B::g() const
    virtual void h(char); // overrides B::h(char)
    void k(); // doesn't override
};

class Derived2: public Base
{
public:
    void f() override; // OK - overrides Base::f()
    void g() override; // error - doesn't override B::g() const
    virtual void h(char); // overrides B::h(char)
    void k() override; // error - B::k() is not virtual
};

Blokowanie dziedziczenia lub nadpisywania metod - final

class NoInheritable final
{
    // ...
};

class Derived : public NoInheritable // error - base marked as final
{
    // ...
};

class Base
{
public:
    virtual void f() const;
};

class Derived1 : public Base
{
public:
    virtual void f() const final; // enables additional optimization
};

class Derived2 : public Derived1
{
public:
    virtual void f() const; // error - f() attempts to override Derived1::f()
                            // marked as final
};