Wikipedysta:Wojciech mula/Brudnopis

Część 1: Podstawowe mechanizmy C++

  • brakuje r-referencji (C++11)
  • brakuje decltype (szczegóły w metaprogramowaniu)
  • brakuje lambd
  • inteligentne wskaźniki
  • przeciążanie new i delete
  • rozdział o "inline" od usunięcia/znaczącego przeredagowania, bo stracił na aktualności kilka lat temu

Część 2: Podstawy programowania obiektowego

  • konstruktory: delegacja, kasowanie, konstruktory domyślne, konstruktory prywatne, explicit

Część 3: Zaawansowane programowanie obiektowe

  • opisać wzorzec RAII
  • opisać dynamic_cast
  • dziedziczenie: final (C++11)
  • dziedziczenie z virtual

Część 4: Zaawansowane konstrukcje językowe

  • metaprogramowanie (SFINAE + sporo przykładów, type_info)

Duże zmiany:

  • wyrażenia regularne (C++11) do części 5
  • rozdział o wątkach (C++11)
  • zaprzyjaźnianie klas/funkcji

Specjalizacja szablonu

edytuj

Jeśli z jakiś powodów ogólna definicja klasy bądź funkcji szablonowej nie jest wystarczająca, wówczas można dla konkretnych wartości parametrów utworzyć specjalizację, która zostanie użyta zamiast ogólnej definicji, wtedy gdy nastąpi dopasowanie argumentów. W programie może istnieć wiele specjalizacji tego samego szablonu. Dla parametrów szablonu, które są typami w konkretyzacji należy podać nazwy typów; dla parametrów nie będących typami są to stałe wartości.

Składnia specjalizacji jest następująca:

template <>                 // pusta lista parametrów
class Nazwa<parametery> {   // po nazwie klasy lista wartości i typów
    // ...
};

Specjalizacja funkcji jest analogiczna, np.

template <>
int Funkcja<int, bool, double>() {
    // ...
}

W przypadku klas, klasa specjalizowana nie ma dostępu do metod i pól parametryzowanej klasy, są to odrębne byty. Dlatego jeżeli specjalizacja i ogólna wersja mają posiadać wspólne metody (lub pola) muszą odziedziczyć po innej klasie zawierającej te elementy.


Przykład z szablonem parametryzowanym typem

edytuj
#include <iostream>

template <typename T>
class Szablon {
public:
    void wyswietl() {
        std::cout << "Szablon<T>" << '\n';
    }
};

template <>
class Szablon<int> {
public:
    void wyswietl() {
        std::cout << "Szablon<int>" << '\n';
    }
};

int main() {

    Szablon<double> x;
    Szablon<bool> y;
    Szablon<int> z;

    x.wyswietl();
    y.wyswietl();
    z.wyswietl();

    return 0;
}

Program wyświetli:

Szablon<T>
Szablon<T>
Szablon<int>

Przykład z szablonem parametryzowanym wartością

edytuj

W tym przykładzie funkcja liczy potęgę o wykładniku całkowitym. W ogólnej wersji używa uniwersalnej funkcji bibliotecznej z biblioteki standardowej, lecz dla kilku przypadków została wyspacjalizowana.

#include <cmath>

template <int Wykladnik>
double potega(double x) {
    return pow(x, Wykladnik);
}

template <>
double potega<0>(double x) { // x^0 = 1
    return 1.0;
}

template <>
double potega<1>(double x) { // x^1 = x
    return x;
}

template <>
double potega<2>(double x) { // x^2 = x*x
    return x*x;
}


int main() {
    
    double y1 = potega<0>(5.0);
    double y2 = potega<1>(5.0);
    double y3 = potega<2>(5.0);
    double y4 = potega<17>(5.0);
}

Statyczne asercje

edytuj

Błędy w programach można wykrywać w czasie kompilacji lub w czasie wykonywania. Błędy czasu kompilacji były jednak ograniczone do dyrektywy preprocesora #error. Błędy czasu wykonywania to klasyczne asercje lub wyjątki zgłaszane do zasygnalizowania problemu.

W C++11 wprowadzono statyczne asercje, które są sprawdzane w czasie kompilacji i gdy test nie przechodzi kompilacja jest przerywana. Składnia jest następująca:

static_assert(warunek, "komunikat błędu"); // komunikat błędu będzie wypisany przez kompilator

Warunek może zawierać dowolne operatory logiczne i nawiasy, chociaż jeśli to możliwe dobrze jest rozdzielać testowane własności, tak żeby łatwo było dojść przyczyny problemu.

Statyczne asercje mają szczególne znaczenie w programowaniu z użyciem szablonów, pozwalają bowiem testować nie tylko wartości, ale również cechy typów. Np. możemy ograniczyć szablon do parametrów typu numerycznego, typów konwertowalnych do innego typu, bądź klas dziedziczących po określonej klasie. W pliku nagłówkowym type_traits jest zdefiniowanych wiele podstawowych testów, nie jest też specjalnie trudne tworzenie własnych.

Na przykład poniższego programu nie da się skompilować, ponieważ typ std::string nie jest numeryczny:

#include <string>
#include <type_traits>  // std::is_arithmetic

template <typename T>
class Policz {
    
    static_assert(std::is_arithmetic<T>::value, "T musi być typem numerycznym");

};

int main() {
    Policz<std::string> p;
}

Deklaracja funkcji

edytuj

Deklaracja funkcji możliwa jest na dwa sposoby, tradycyjny, taki jak w C:

typ_zwracany nazwa_funkcji(lista parametrów);

oraz nowy, dostępny od C++11:

auto nazwa_funkcji(lista parametrów) -> typ_zwracany;

W przypadku zwykłych funkcji drugi sposób umożliwia krótsze definicje, np.

// plik .h
namespace program {
    namespace podsystem {
        
        typedef int typ;

        typ funkcja_1();
        typ funkcja_2();
    }
}

// plik .cpp
program::podsystem::typ program::podsystem::funkcja_1() {

    return 42;
}

auto program::podsystem::funkcja_2() -> typ {

    return 42;
}

Deklaracja funkcja_1 jest w starym stylu, zarówno dla typu zwracanego, jak i samej funkcji konieczna jest pełna kwalifikacja przestrzeni nazw. Natomiast w deklaracji funkcja_2 wystarczy kwalifikacja samej funkcji, później odwołanie do typ jej nie wymaga, gdyż zakres widoczności jest znany.

Nowy zapis ma o wiele istotniejsze znaczenie przy szablonach, w przypadku gdy funkcja szablonowa ma zwracać wartości typu, który zależy od argumentów szablonu, np.

template <typename Ta, typename Tb>
??? dodaj(Ta a, Tb b) {
    return a + b;
}

Operator + dla Ta i Ta może zwrócić cokolwiek, nie wiemy co. Można posiłkować się decltype, ale zapis jest koszmarny:

template <typename Ta, typename Tb>
decltype(*(Ta*)0 + *(Tb*)0) dodaj(Ta a, Tb b) {
    return a + b;
}

Dzięki nowej składni zapis staje się bardzo czytelny, ponieważ argumenty funkcji stają się dostępne:

template <typename Ta, typename Tb>
auto dodaj(Ta a, Tb b) -> decltype(a + b) {
    return a + b;
}

Przy okazji widać, że wyrażenie w funkcji i w decltype są identyczne, więc to też niepotrzebne powtórzenie i okazja do błędu. W kolejnym standardzie C++ przewiduje się dedukcję typów zwracanych, dokładnie tak, jak ma to obecnie miejsce w przypadku funkcji anonimowych.

Następna sekcja

edytuj