Pokazywanie postów oznaczonych etykietą cpp_repeat. Pokaż wszystkie posty
Pokazywanie postów oznaczonych etykietą cpp_repeat. Pokaż wszystkie posty

12 lutego 2020

[C++17] Parallel algorithms

Biblioteka standardowa w C++17 została rozbudowana o algorytmy, których praca może zostać zrównoleglona. O sposobie wykonania decyduje ExecutionPolicy przekazane do funkcji jako argument. Programista musi zapewnić, że funkcja przekazana do "algorytmu", będzie bezpieczna - nie będzie zależności między danymi (np. modyfikowanie danych, które mogą być odczytywane przez inny równoległy wątek). Dobre wytłumaczenie różnic na stackoverflow, a tutaj małe zestawienie:
  • std::execution::seq - standardowe wykonanie sekwencyjne, bez zrównoleglenia.
  • std::execution::par - równoległe wykonanie (choć nie ma obietnicy, że tak się stanie).
  • std::execution::par_unseq - równoległe wykonanie (choć nie ma obietnicy, że tak się stanie). Wymaga silniejszych gwarancji na to że przeplatane wywołanie funkcji będzie bezpieczne także w obrębie jednego wątku. Nowe procesory oferują taką możliwość dzięki instrukcjom do wektoryzacji - SIMD (Single-Instruction, Multiple-Data) parallelism.
Przykład z funkcją std::reduce, która w działaniu przypomina std::accumulate. Zsumowanie wartości w wektorze:
#include <iostream>
#include <numeric>
#include <execution>
#include <vector>

using namespace std;

int main() {
    vector<int> vec{1, 2, 3, 4};

    int result = std::reduce(std::execution::par,
                             begin(vec),
                             end(vec));

    cout << result << endl;
}
W przypadku gcc (9.2.1 20191008) wymagane było zainstalowanie dodatkowej paczki libtbb-dev (Threading Building Blocks).
$ sudo apt-get install libtbb-dev
$ g++ -std=c++17 main.cpp -ltbb
$ ./a.out

10

11 lutego 2020

[C++] Atomics

Nie miałem do tej pory wiele do czynienia z atomic-ami (inny wpis) w prawdziwym życiu i traktuje je jako niskopoziomowy mechanizm, ale inni chyba lepiej potrafią wykorzystać ich możliwości. Pozwalają na pisanie kodu "lock-free", chociaż bez głębszego zrozumienia ich natury, niekoniecznie będzie to kod szybszy od tego opartego na muteksach. Ciekawy wykład na ich temat poprowadził Fedor Pikus na CppCon 2017: C++ atomics, from basic to advanced. What do they really do? Warto obejrzeć więcej niż raz.

Operacje na atomic-ach odzwierciedlają operacje sprzętowe i gwarantują, że zostaną wykonane w jednej transakcji (atomowo). CPU oferuje sporą liczbę mechanizmów, które są z nimi związane, z tego też względu standardowa biblioteka jest całkiem rozbudowana. Atomic-iem, może być każdy prymitywny typ (tylko takie obiekty mogą pojawić się w rejestrach CPU).
Przykłady:
// Dla
std::atomic<int> x{0};

// Operacje:
++x;           // atomowy pre-increment
x++;           // atomowy post-increment
x += 1;        // atomowy increment
int y = x * 2; // atomowy odczyt x
x = y + 2;     // atomowy zapis do x

// Uwaga, ta operacja jest niewspierana 
x *= 2;        // ERROR

// Atomowy odczyt x, po którym następuje atomowy zapis do x (dwie operacje)
x = x * 2;
W przykładzie poniżej, atomic posłużył do blokowania wątków, tak aby funkcje even/odd drukowały naprzemiennie tekst w momencie inkrementacji. Uwaga, nie ma gwarancji, że wartość counter wyświetlana na ekranie będzie zgodna z tym co było sprawdzane w if. Są to dwie atomowe operacje odczytu z pamięci, a wartość counter może się zmienić pomiędzy nimi.
#include <iostream>
#include <thread>
#include <atomic>

using namespace std;

std::atomic<int> counter{0};

void odd(size_t n) {
    for (size_t i = 0; i < n; i++) {
        if (counter % 2 == 1) {
            cout << "Odd  increment: " << counter << endl;
            counter++;
        } else {
            cout << "Odd  check: " << counter << endl;   // wartość mogła się zmienić
        }

        std::this_thread::sleep_for(std::chrono::milliseconds{20});
    }
}

void even(size_t n) {
    for (size_t i = 0; i < n; i++) {
        if (counter % 2 == 0) {
            cout << "Even increment: " << counter << endl;
            counter++;
        } else {
            cout << "Even check: " << counter << endl;   // wartość mogła się zmienić
        }
        std::this_thread::sleep_for(std::chrono::milliseconds{40});
    }
}

int main() {
    constexpr size_t steps{6};
    std::thread t1{odd, steps};
    std::thread t2{even, steps};

    t1.join();
    t2.join();
}

Wynik:
Odd  check: 0
Even increment: 0
Odd  increment: 1
Even increment: 2
Odd  increment: 3
Odd  check: 4
Even increment: 4
Odd  increment: 5
Odd  check: 6
Even increment: 6
Even check: 7
Even check: 7

8 lutego 2020

[C++17] std::variant i std::visit

Nowa funkcja std::visit w C++17 umożliwia wywołanie funkcji na rzecz jakiegoś obiektu. Mając wiele takich obiektów (np. "odwiedzając" w pętli obiekty zachowane w jakimś kontenerze) mamy gotowy wzorzec wizytator (odwiedzający). Typowym sposobem implementacji takiego wzorca w C++ było skorzystanie z polimorfizmu. std::visit pozwala jednak na wywołanie funkcji na dowolnym obiekcie. Klasy nie muszą mieć wspólnego interfejsu, ale trzeba będzie skorzystać z znanego z biblioteki boost std::variant. W ten sposób można przechowywać obiekty alternatywnych typów (type-safe union).
#include <iostream>
#include <vector>
#include <variant>

using namespace std;

struct Circle {
    void show() const { cout << "Circle" << endl; }
};

struct Rect {
    void show() const { cout << "Rect" << endl; }
};

int main() {
    using Variant = std::variant<Circle, Rect>;

    vector<Variant> vec;
    vec.push_back(Circle{});
    vec.push_back(Rect{});

    for (const auto& v : vec) {
        std::visit([](const auto& obj){ obj.show(); }, v);
    }
}
Wynik:
Circle
Rect

7 lutego 2020

[C++11/17] enable_if vs. if constexpr

enable_if pojawiło się w C++11 i miało za zadanie uelastycznić mechanizm metaprogramowania w szablonach, pozwalając na tworzenie specjalizacji typu, tylko gdy typ spełniał określone warunki. Wraz z type_traits dało się stworzyć naprawdę trudny w zrozumieniu i utrzymaniu kod. Obecnie sam core guadline zaleca, aby jego używanie ograniczyć do minimum, wymieniając enable_if jako przykład złego kodu. Na szczęście wszystko wskazuje na to, że społeczność chce wreszcie zabić nieszczęsne metaprogramowanie w szablonach. Do tego jeszcze jednak długa droga.

Oryginalnym mechanizmem metaprogramowania w C++ były znane z języka C makra, lecz pewnego dnia, Erwin Unruh, przez przypadek odkrył mechanizm dziś znany jako SFINAE (Substitution Failure Is Not An Error). W skrócie, jeżeli kompilatorowi nie uda się wyspecjalizować szablonu będzie próbował dalej. W jego przykładzie wywołując rekursywną specjalizację szablonów, dokonał obliczeń w czasie kompilacji, choć kompilacja zakończyła się niepowodzeniem. Być może, właśnie przez to, że mechanizm ten pojawił się przypadkiem, a nie został wprowadzony z premedytacją, nie jest on przemyślany składniowo, sprawia że wiele osób odrzuca i dzieli społeczność C++ na dwa obozy.

Przykłady poniżej oparłem na prezentacji Nicolai Josuttis - C++17 - The Best Features. Na początek wersja kodu wykorzystująca enable_if przez zwracany typ (drugi parametr - w naszym przypadku std::string).
#include <iostream>
#include <type_traits>

using namespace std;

template<typename T>
std::enable_if_t<std::is_arithmetic_v<T>,
                 std::string>
as_string1(T x) {
    return std::to_string(x) + " [is_arithmetic_v]";
}

template<typename T>
std::enable_if_t<std::is_same_v<T, std::string>,
                 std::string>
as_string1(T x) {
    return x + " [is_same_v]";
}

template<typename T>
std::enable_if_t<!std::is_same_v<T, std::string> && !std::is_arithmetic_v<T>,
                 std::string>
as_string1(T x) {
    return std::string(x) + " [!is_same_v && !is_arithmetic_v]";
}

int main() {
    cout << as_string1(11) << endl;
    cout << as_string1(std::string("12")) << endl;
    cout << as_string1("13") << endl;
}
Wynik:
11 [is_arithmetic_v]
12 [is_same_v]
13 [!is_same_v && !is_arithmetic_v]
Inna wersja, gdzie enable_if pojawia się jako parametr szablonowy. Domyślne argumenty szablonowe, nie są częścią sygnatury funkcji szablonowej, trzeba więc do enable_if przekazać jeszcze dummy typ (int), nie wiem czemu trzeba przypisać do tego 0. Dziwny hack.
#include <iostream>
#include <type_traits>

using namespace std;

template<typename T, std::enable_if_t<std::is_arithmetic_v<T>,
                                      int> = 0>
std::string as_string2(T x) {
    return std::to_string(x) + " [is_arithmetic_v]";
}

template<typename T, std::enable_if_t<std::is_same_v<T, std::string>,
                                     int> = 0>
std::string as_string2(T x) {
    return x + " [is_same_v]";
}

template<typename T, std::enable_if_t<!std::is_same_v<T, std::string> && !std::is_arithmetic_v<T>,
                                      int> = 0>
std::string as_string2(T x) {
    return std::string(x) + " [!is_same_v && !is_arithmetic_v]";
}

int main() {
    cout << as_string2(21) << endl;
    cout << as_string2(std::string("22")) << endl;
    cout << as_string2("23") << endl;
}
Wynik:
21 [is_arithmetic_v]
22 [is_same_v]
23 [!is_same_v && !is_arithmetic_v]
Są jeszcze inne formy zapisu enable_if np. jako parametr funkcji, ale w tej chwili nie jest to istotne. W C++17 pojawił się ciekawy mechanizm if constexpr, który pozwala radzić sobie z dużą liczbą takich specjalizacji, a kod wygląda znacznie bardziej czytelnie.
#include <iostream>
#include <type_traits>

using namespace std;

template<typename T>
std::string as_string3(T x) {
    if constexpr(std::is_arithmetic_v<T>) {
         return std::to_string(x) + " [is_arithmetic_v]";
    } else if constexpr(std::is_same_v<T, std::string>) {
         return x + " [is_same_v]";
    } else {
         return std::string(x)  + " [!is_same_v && !is_arithmetic_v]";
    }
}

int main() {
    cout << as_string3(31) << endl;
    cout << as_string3(std::string("32")) << endl;
    cout << as_string3("33") << endl;
}
Wynik:
31 [is_arithmetic_v]
32 [is_same_v]
33 [!is_same_v && !is_arithmetic_v]

17 października 2015

[C++11] std::future - programowanie oparte na zadaniach

W programowaniu współbieżnym preferowanym podejściem powinno być programowanie oparte na zadnich, a nie na wątkach [1]. Kilka przydatnych linków:
std::future jest obiektem, przechowującym wyniki (także wyjątki) asynchronie działających funkcji - czyta wynik ze współdzielonego stanu. Wynik można pozyskać za pomocą metody get(). W takim scenariuszu wątek główny zostanie zablokowany do chwili aż wynik będzie dostępny, lub zwróci wynik natychmiast, jeżeli asynchroniczna operacja zakończyła się wcześniej.

Do asynchronicznego wołania funkcji służy std::async. Jest to rozwiązanie najbardziej wysokopoziomowe, gdyż sam std::async dba o to by ustawić std::future w stan ready. Wywołanie oprócz ewentualnych argumentów jakie mają być przekazane do funkcji wymaga również podania policy:
  • std::launch::async - nowy wątek zostanie uruchomiony do wykonania zadania
  • std::launch::deferred - wykonanie zadania zostaje odroczone do momentu wywołania metod get() lub wait()
  • std::launch::async | std::launch::deferred - kombinacja flag, jednak zachowanie jest zależne od implementacji
Wszystkie przykłady kompilowałem w ten sam sposób, z dodatkowo włączonym sanitizerem, który pomógł wykryć kilka błędów w kodzie.
$ clang++ -std=c++14 -fsanitize=thread -lpthread -g main.cpp
Przykład obliczania ciągu Fibonacciego z zastosowaniem std::async.
#include <iostream>
#include <future>

using namespace std;

int fibonacci(int n)
{
    std::cout << "Current n: " << n << std::endl;
    if (n == 0)
        return 0;
    else if (n == 1)
        return 1;
    return fibonacci(n - 1) + fibonacci(n - 2);
}

int main()
{
    std::future<int> result = std::async(std::launch::async | std::launch::deferred,
                                         fibonacci, 3);
    std::cout << "Result: " << result.get() << std::endl;

    return 0;
}
Wynik:
Current n: 3
Current n: 2
Current n: 1
Current n: 0
Current n: 1
Result: 2
std::packaged_task także pozawala na tworzenie obiektów std::future. Można go porównać do std::function, a więc jako wrapper do tworzenia obiektów callable. W przeciwieństwie do std::async, nie uruchamia on jednak przekazanej funkcji automatycznie. Najważniejszą właściwością std::packaged_task jest możliwość pozyskania z niego obiektu std::future, i przekazania go do innego wątku, gdzie zostanie wykonany.

Przykład:
#include <iostream>
#include <thread>
#include <future>

using namespace std;

int fibonacci(int n)
{
    std::cout << "Current n: " << n << std::endl;
    if (n == 0)
        return 0;
    else if (n == 1)
        return 1;
    return fibonacci(n - 1) + fibonacci(n - 2);
}

int main()
{
    std::packaged_task<int(int)> task{fibonacci};
    std::future<int> result = task.get_future();

    std::thread t{std::move(task), 3};
    t.join();

    std::cout << "Result: " << result.get() << std::endl;

    return 0;
}
Wynik:
Current n: 3
Current n: 2
Current n: 1
Current n: 0
Current n: 1
Result: 2
std::promise można rozważyć jako początek kanału komunikacyjnego, na którego końcu znajduje się std::future. Do jego zadań, należy zapisanie wyniku do współdzielonego stanu. Czasami dane, które nas interesują są już dostępne (obliczone) i nie musimy czekać na zakończenie jakiegoś wątku. Za pomocą std::promise możemy w wątku głównym wytworzyć obiekt std::future (czyli końcówka kanału - dzięki metodzie get_future()), a następnie przekazać go (std::promise) do innego wątku. W wątku pobocznym, gdy interesujące nas dane zostaną obliczone wołamy metodę set_value() (początek kanału). W ten sposób obiekt std::future zostanie ustawiony w stan ready, i dane będą mogły być odczytane w wątku głównym. W porównywaniu do std::async, jest to rozwiązaniem bardziej nisko poziomowe, lecz czasami zachodzi potrzeba aby z niego skorzystać.

std::promise ma jeszcze jedno zastosowanie, można go wykorzystać jako mechanizm do sygnalizacji pomiędzy wątkami (std::promise<void> - typ obiektu jako void). Coś na kształt std::condition_variable, z tym że taka sygnalizacja może zadziać się tylko raz. Aby odblokować pracę na współdzielonych danych należy wywołać set_value(). Aby wstrzymać pracę wątku do momentu, aż dane będą dostępne, należy wywołać metodę wait().

Przykład:
#include <iostream>
#include <thread>
#include <future>

using namespace std;

struct Fibonacci {
    std::promise<int> p;
    bool isSatisfied;
    const int index;
    int partialIndex;

    Fibonacci(std::promise<int> p_, int index_)
        : p(std::move(p_))
        , isSatisfied(false)
        , index(index_)
        , partialIndex(0)
    {
    }

    int fibonacci(int n)
    {
        std::cout << "Current n: " << n << std::endl;

        int result = 0;
        if (n == 0)
            result = 0;
        else if (n == 1)
            result = 1;
        else
            result = fibonacci(n - 1) + fibonacci(n - 2);

        if (isSatisfied == false and partialIndex == n) {
            isSatisfied = true;
            std::cout << "Passing partial result" << std::endl;

            p.set_value(result);
        }
        return result;
    }

    void operator()(int partialVal_)
    {
        partialIndex = partialVal_;
        fibonacci(index);
    }
};

int main()
{
    std::promise<int> p;
    std::future<int> result = p.get_future();

    std::thread t{ Fibonacci{ std::move(p), 5 }, 3 };

    std::cout << "Result: " << result.get() << std::endl;
    t.join();

    return 0;
}
Przykładowy wynik:
Current n: 5
Current n: 4
Current n: 3
Current n: 2
Current n: 1
Current n: 0
Current n: 1
Passing partial result
Current n: 2
Current n: 1
Current n: 0
Current n: 3
Current n: 2
Result: 2Current n: 
1
Current n: 0
Current n: 1
Bibliografia:
  • [1] Scotta Meyersa: Skuteczny nowoczesny C++. APN PROMISE SA, 2015. Rozdział 7, str. 289.
  • [2] Anthony Williams: C++ Concurency in Action. USA Manning publications Co., 2012. Rozdział 7, str. 67.

2 października 2015

[C++11] std::condition_variable i współdzielenie danych między wątkami

Praca na danych wymaga mechanizmu, dzięki któremu wątki będą mogły zablokować swoje działania do czasu, aż z danych nie będzie korzystał żaden inny wątek. Najprostszym i zarazem najmniej efektywnym rozwiązaniem może być pętla i próba założenia blokady na muteks. Biblioteka standardowa na szczęście oferuje znacznie lepsze rozwiązanie w postaci std::condition_variable.

std::condition_variable wymaga do działania muteksów i std::unique_lock (ze względu na efektywność). Pozwala to na większą elastyczność, gdyż muteks może zostać zablokowany i odblokowany w dowolnym momencie (std::lock_guard wykorzystuje RAII). Klasa posiada dwa rodzaje metod: wait_* (wait, waif_for, wait_until) oraz notify_* (notify_one, notify_all). Te pierwsze blokują działanie wątku do czasu, aż zostanie spełniony warunek który został przekazany do wait_*. Może to być lambda, punkt w czasie lub odcinek czasu. Natomiast notify_* zajmuje się wybudzaniem wątków.

Ciekawostka, o której dowiedziałem się z cppreference. Przed wołaniem notify_*, należy ręczenie zawołać unlock na obiekcie std::unique_lock (choć destruktor tego obiektu i tak by to wykonał). W ten sposób unikamy sytuacji, w której wybudzamy wątek, tylko po to by ponownie go uśpić, ponieważ warunek nie został jeszcze osiągnięty.
Przykład.
#include <condition_variable>
#include <thread>
#include <mutex>
#include <list>
#include <string>
#include <iostream>

using namespace std;

std::mutex m;
std::condition_variable cv;
std::list<string> train;

void dig_coal() {
    int resource = 4;
    while(resource >= 0) {
        std::unique_lock<std::mutex> l{m};
        cv.wait_for(l, std::chrono::milliseconds{400});

        if (resource > 0) train.push_back("coal");
        else train.push_back("empty");
        resource -= 1;
        std::cout << "Added trolley, train length: " << train.size() << std::endl;

        l.unlock();
        cv.notify_one();
    }

    std::cout << "No more coal to mining." << endl;
}

void burn_coal() {
    while(true) {
        std::unique_lock<std::mutex> l{m};
        cv.wait(l, []{ return not train.empty(); });

        const string trolley = train.front();
        train.pop_front();
        std::cout << "Coal burn, train length: " << train.size() << std::endl;

        l.unlock();
        cv.notify_one();

        if(trolley == "empty") break;
        std::this_thread::sleep_for(std::chrono::milliseconds{600});
    }

    std::cout << "All coal burn." << endl;
}

int main() {
    std::thread miner{dig_coal};
    std::thread power_station{burn_coal};

    cv.notify_one();

    miner.join();
    power_station.join();

    return 0;
}
Wynik:
Added trolley, train length: 1
Coal burn, train length: 0
Added trolley, train length: 1
Added trolley, train length: 2
Coal burn, train length: 1
Added trolley, train length: 2
Added trolley, train length: 3
No more coal to mining.
Coal burn, train length: 2
Coal burn, train length: 1
Coal burn, train length: 0
All coal burn.

3 września 2015

[C++11] Muteksy i współdzielenie danych między wątkami

Wstęp

Biblioteka standardowa C++11 wprowadza klasę std::lock_guard typu RAII implementującą idiom muteksu (blokuje muteks w konstruktorze i zwalnia go w destruktorze). Stosując muteksy należy przyswoić sobie dwie ważne zasady:
  1. Nie wolno zwracać lub zapisywać wskaźników lub referencji poza zasięgiem lock-a (blokady).
  2. Należy blokować zasoby tylko przez minimalny okres czasu, potrzebny do wykonania danej operacji.
Przykład poniżej pokazuje wykorzystanie std::lock_guard.
#include <iostream>
#include <string>
#include <thread>
#include <mutex>

using namespace std;

class Storage {
private:
    std::mutex interanl_mutex;
    std::string buff;

public:
    void increment(const std::string id) {
        std::lock_guard<std::mutex> guard(interanl_mutex);
        buff += id;
    }

    void show() {
        std::lock_guard<std::mutex> guard(interanl_mutex);
        std::cout << buff << endl;
    }
};

void update(const std::string id, int loop_counter, Storage& storage) {
    for (int i = 0; i < loop_counter; ++i) {
        std::this_thread::sleep_for(std::chrono::seconds(1));
        storage.increment(id);
    }
}

int main() {
    Storage s;
    std::thread t1{update, std::string("a"), 2, std::ref(s)};
    std::thread t2{update, std::string("b"), 3, std::ref(s)};

    t1.join();
    t2.join();

    s.show();
    return 0;
}
Wynik:
ababb

Deadlock - zakleszczenie

Z samym zagadnieniem można zapoznać się tutaj:
Kilka zasad dzięki, którym można uniknąć tego rodzaju problemów:
  1. Nie wolno czekać na inny wątek jeżeli istnieje szansa, że to on może czekać na nas.
  2. Nie wolno zakładać blokad, jeżeli jakaś została już założona. Jeżeli wątek potrzebuje kilku muteksów należy wykonać blokowanie jako pojedynczą operację (stosując np. std::lock)
  3. Jeżeli tworzona jest biblioteka, nie należy wołać kodu użytkownika trzymając jednocześnie blokadę na muteksach. Nigdy nie można być pewnym, czy kod użytkownika też nie założy swojej własnej blokady.
  4. Jeżeli nie można skorzystać z std::lock do założenia kilku blokad jednocześnie, należy pamiętać by blokady zakładać zawsze w tej samej kolejności.
  5. Nie wolno zakładać blokad, jeżeli kod posiada już blokadę na muteksa z niższego poziomu (można stworzyć hierarchical_mutex, który nie jest częścią standardu ale jest łatwy w implementacji [1])
W tym miejscu należy wspomnieć o nowej klasie std::unique_lock, którą można stosować tam gdzie należy odroczyć blokowanie muteksów, albo gdy istnieje potrzeba przetransferowania własności do innego obiektu. Ma ona podobny konstruktor do std::lock_guard.
Drugi parametr konstruktora może przyjmować kilka interesujących parametrów:
  • std::adopt_lock jest używany by zaznaczyć, że muteks został już zablokowany i std::lock_guard albo std::unique_lock powinien tylko zaadoptować własność, zamiast próbować go jeszcze raz blokować.
  • std::defer_lock występuje tylko dla std::unique_lock i mówi o tym że muteks nie powinien zostać zablokowany przez konstruktor. Można tą operację wykonać później wołając np. funkcję std::lock.
Dwa przykłady jak może wyglądać zakładanie kilku blokad jednocześnie:
std::mutex mutex1;
std::mutex mutex2;

std::lock(mutex1, mutex2);
std::lock_guard<std::mutex> lock1(mutex1, std::adopt_lock);
std::lock_guard<std::mutex> lock2(mutex2, std::adopt_lock);
Alternatywa z wykorzystaniem std::unique_lock:
std::mutex mutex1;
std::mutex mutex2;

std::unique_lock<std::mutex> lock1(mutex1, std::defer_lock);
std::unique_lock<std::mutex> lock2(mutex2, std::defer_lock);
std::lock(lock1, lock2);

Inicjowanie zasobów współdzielonych przez wątki

Często zachodzi potrzeba zainicjowania zmiennej tylko przez jeden z wątków. Zamiast samodzielnie tworzyć kod blokujący zasób i sprawdzający czy został on zainicjowany, biblioteka standardowa wprowadza specjalny mechanizm w postaci std::call_flag i std::call_once, które pozwalają na wykonanie takiej operacji (inicjowania) dokładnie raz w bezpieczny sposób.

W C++11 istnieje konieczność stosowania std::ref w przypadku argumentów do funkcji inicjującej, inaczej kompilator zaprotestuje. Zostało to naprawione w C++17.
#include <iostream>
#include <string>
#include <vector>
#include <thread>
#include <mutex>

using namespace std;

std::mutex vec_mutex;
std::once_flag vec_flag;

void show(std::string description, std::vector<int>& vec) {
    std::cout << description;
    for(const auto& v : vec)
        std::cout << v << " ";
    std::cout << std::endl;
}

void init(std::vector<int>& vec) {
    for(auto& v : vec)
        v = 0;
    show("Initialize (call_once):    ", vec);
}

void update(std::vector<int>& vec, int index) {
//  std::call_once(vec_flag, init, std::ref(vec));   // Działa w C++11
    std::call_once(vec_flag, init, vec);             // Działa w C++17
    std::lock_guard<std::mutex> guard(vec_mutex);
    vec[index] += 1;
}

int main() {
    std::vector<int> vec = {9, 9, 9};
    show("Creation (in main thread): ", vec);

    std::thread t1{update, std::ref(vec), 0};
    std::thread t2{update, std::ref(vec), 2};

    t1.join();
    t2.join();

    show("All thread finish:         ", vec);

    return 0;
}
Wynik:
Creation (in main thread): 9 9 9 
Initialize (call_once):    0 0 0 
All thread finish:         1 0 1 

Bibliografia

  • [1] Anthony Williams: C++ Concurency in Action. USA Manning publications Co., 2012. Rozdział 3, str. 33.

30 maja 2015

[C++11] Przekazywanie zmiennych do wątku

Najpierw kilka przydatnych metod i obiektów do eksperymentowania z std::thread i std::this_thread.
  • hardware_concurrency - informuje ile wątków ze wsparciem sprzętowym jest dostępnych w systemie. Jeżeli planujemy zrównoleglić naszą pracę, trzeba pamiętać, że jeden wątek jest już zajęty przez nasz program.
  • get_id - metoda wołana z std::this_thread albo na obiekcie wątku. Pozwala zorientować się z jakim wątkiem mamy do czynienia. Standard nie definiuje co to będzie, ale gwarantuje, że taki obiekt (std::thread::id), może być porównywany i wyświetlany na standardowym wyjściu.
  • sleep_for - pozwala uśpić wątek na pewien czas.

Konstruktor std::thread ma podobną konstrukcję do std::bind. Trzeba pamiętać, że domyślnie wszystkie przekazane zmienne są kopiowane. Gdy interesuje nas aby wątek wyliczył i zapisał jakąś wartość do zmiennej przekazanej przez referencję, należy skorzystać z std::ref (tak jak w przypadku std::bind).
#include <iostream>
#include <thread>

using namespace std;

struct Presenter {
    void operator()(int& value) {
        cout << "Slave id: " << std::this_thread::get_id() << endl;
        value += 100;

        std::this_thread::sleep_for(std::chrono::seconds(2));
    }
};

int main() {
    cout << "Possible threads: " << std::thread::hardware_concurrency() << endl;
    cout << "Master id: " << std::this_thread::get_id() << endl;

    int value = 1;

    std::thread t{ Presenter(), std::ref(value) };
    cout << "Child id from master: " << t.get_id() << endl;

    t.join();
    return 0;
}
Wynik:
Possible threads: 4
Master id: 140557572298560
Child id from master: 140557555422976
Slave id: 140557555422976
Kłopotliwe mogą okazać się wszelkie niejawne rzutowania. W przykładzie poniżej, funkcja fun() przyjmuje jako argument std::string. Wykonanie programu będzie przebiegało w ten sposób. W pierwszej kolejności do wątku przekazany zostanie wskaźnik (char const*), a następnie wykonana zostanie konwersja do std::string (już w kontekście nowego wątku). Istnieje bardzo duża szansa, że gdy konwersja się zadzieje, zmienna z głównego wątku nie będzie już istnieć, a działanie programu będzie trudne do przewidzenia. Rozwiązaniem jest wykonanie jawnego rzutowania przed przekazaniem zmiennych do std::thread.
#include <iostream>
#include <string>
#include <thread>

using namespace std;

void fun(std::string s) {
    cout << "Received: " << s << endl;
}

int main() {
    char buf[10];
    for (int i = 0 ; i < 3; i++) {
        std::sprintf(buf, "%d", i);
        cout << "Setting: " << buf << endl;

        std::thread t{ fun, buf };
//      std::thread t{ fun, std::string(buf) };    // OK!

        t.detach();
    }
    std::sprintf(buf, "%d", 77);
    std::this_thread::sleep_for(std::chrono::seconds(1));
}
Wynik. Jeden z wątków dokonuje konwersji z char* na std::string, gdy wątek główny zmienił już zawartość bufora na 77 (linijka 22).
Setting: 0
Setting: 1
Received: 1
Received: 2
Setting: 2
Received: 77

19 maja 2015

[C++] Aggregate, trivial classes, standard-layout

Nowy standard wprowadza kilka definicji, które trzeba przyswoić.

Aggregate


Agregaty (tablice lub klasy), które mogą być inicjalizowane nawiasami klamrowymi (nie ma to nic wspólnego z std::initializer_list).
Jeżeli elementów, które podamy w klamrach będzie mniej, niż tych zadeklarowanych, to dla brakujących elementów zostanie (o ile to możliwe) zawołany konstruktor domyślny.
Agragaty, nie mogą mieć:
  • konstruktora stworzonego przez użytkownika (ale user-declared jest w porządku)
  • niestatycznych składowych zainicjowanych w miejscu deklaracji
  • prywatnych lub chronionych niestatycznych pól składowych
  • klas bazowych
  • metod wirtualnych
#include <iostream>
#include <string>
#include <vector>

struct AggregateClass {
//  AggregateClass() {}           // ERROR - stworzone przez użytkownika
    AggregateClass() = default;   // OK - "user-declared"

    int a;
    std::string b;
    std::vector<double> c;
};

int main() {
    AggregateClass ag = {3, "text", {1.41, 3.14}};
}

Trivial classes


Klasy trywialne wspierają statyczną inicjalizację typu. Klasa trywialna to klasa, która ma trywialny konstruktor domyślny i jest trywialnie kopiowalna.
Klasa trywialnie kopiowalna (trivially copyable), to taka, której instancje mogą być kopiowane bitowo np. przy pomocy std::memcpy().

W opisie metod traitsów, są szczegółowe wymagania:
#include <iostream>
#include <type_traits>

struct Trivial {
    Trivial() = default;

    int value;
};

int main() {
    std::cout << std::boolalpha;

    std::cout << std::is_trivial<Trivial>::value << "\n";
    std::cout << std::is_trivially_copyable<Trivial>::value << "\n";
    std::cout << std::is_trivially_default_constructible<Trivial>::value << "\n";
}
Wynik:
true
true
true

Standard-layout


Typy standard-layout mają takie same ułożenie pól w pamięci jak struktury w języku C. Warunki, które muszą spełniać

POD - przestarzałe od C++20


Typy POD, są kompatybilne bitowo ze strukturami języka C. W nowej wersji standardu wprowadzono nową (uszczegółowioną) definicję typów POD (plain old data). Typem POD jest klasa, która jest jednocześnie trivial classes i standard-layout i jest to właściwość spełniona (rekursywnie) dla wszystkich jej pól.
W C++20 std::is_pod zostało usunięte.

Przykład dla POD i standard-layout

#include <iostream>
#include <type_traits>

struct A {
    int field;
};

struct B {
    int field1;
private:
    int field2;
};

int main()
{
    std::cout << std::boolalpha;

    std::cout << "A trivial: " << std::is_trivial<A>::value << "\n";
    std::cout << "A standard_layout: " << std::is_standard_layout<A>::value << "\n";
    std::cout << "A pod: " <<std::is_pod<A>::value << "\n";

    std::cout << "\n";
    std::cout << "B trivial: " << std::is_trivial<B>::value << "\n";
    std::cout << "B standard_layout: " << std::is_standard_layout<B>::value << "\n";
    std::cout << "B pod: " << std::is_pod<B>::value << "\n";
}
Wynik:
A trivial: true
A standard_layout: true
A pod: true

B trivial: true
B standard_layout: false
B pod: false

14 maja 2015

[C++] std::thread is low level primitive

Na obiektach std::thread zawsze należy wołać join() lub detach(), a brak decyzji skutkuje poważnym błędem. Operacje tworzenia obiektu i "decyzja" o jego losie są rozłączne, co niechybnie zachęca do popełniania pomyłek. Większość książkowych przykładów zachęca do pisania własnych wraperów typu RAII, ale dlaczego sam standard nie oferuje czegoś podobnego? Odpowiedź przyniósł bardzo fajny wpis na blogu Andrzeja Krzemieńskigo.
std::thread należy rozpatrywać jako nisko poziomowy (low level) prymityw. std::thread jest tylko budulcem do tworzenia wysoko pozimowych rozwiązań, tak jak delete, jest budulcem, dzięki któremu może istnieć std::unique_ptr. W momencie wprowadzania tego rozwiązania komitetowi standaryzacyjnemu po prostu zabrakło czasu, na coś więcej. Tak jak należy wystrzegać się delete, w produkcyjnym kodzie, tak samo powinno być traktowane std::thread. Być może boost::strict_scoped_thread będzie dobrym rozwiązaniem.
#include <iostream>
#include <thread>
#include <boost/thread/scoped_thread.hpp>

void f()
{
    std::cout << "Hello" << std::endl;
}

int main()
{
    std::thread t1(f);
    t1.join();

    boost::strict_scoped_thread<> t2((boost::thread(f)));  // podwójne nawiasy

    return 0;
}
I jeszcze konfiguracja CMakeLists.txt
project(cpp_thread)
cmake_minimum_required(VERSION 2.8)
aux_source_directory(. SRC_LIST)
add_executable(${PROJECT_NAME} ${SRC_LIST})
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")

find_package(Boost COMPONENTS system thread REQUIRED)
target_link_libraries(${PROJECT_NAME}
    pthread
    ${Boost_SYSTEM_LIBRARY}
    ${Boost_THREAD_LIBRARY}
)

28 sierpnia 2014

[C++] RAII - Resource Acquisition Is Initialization

Notatka z wykładu Michaela Caissea "Introduction to Modern C++ Techniques" na C++Now (dawniej BoostCon), dotycząca wzorca projektowego RAII. Choć mam okazję korzystać z jej dobrodziejstw przy okazji boost-a, oraz biblioteki standardowej to jednak sam nigdy nic nie napisałem. Technika polega na przyjęciu zasobu, podczas konstruowania obiektu i zwalniania podczas jego destrukcji. Programista nie musi pamiętać o wykonaniu dodatkowych czynności, gdy zasób już nie będzie potrzebny, zwalnianiem zajmie się kompilator.
Przykład poniżej pokazuje zarządzeniem uchwytem do pliku (w konstruktorze plik jest otwierany, w destruktorze niszczony), za pomocą funkcji znanych z C.
#include <iostream>
#include <string>
#include <cstdio>

class FileManager {
public:
    FileManager(const std::string& filePath) {
        std::cout << "Open file" << std::endl;
        _fileHandler = std::fopen(filePath.c_str(), "w+");
    }

    void write(const std::string& str) {
        if(_fileHandler == nullptr)
            return;

        if(std::fputs(str.c_str(), _fileHandler) == EOF)
            throw std::exception();
    }

    ~FileManager() noexcept {
        std::cout << "Close file" << std::endl;
        if(_fileHandler != nullptr)
            std::fclose(_fileHandler);
    }

private:
    FILE* _fileHandler;
};

void modifyFile() {
    FileManager f("aaa.txt");
    f.write("some text\n");
}

int main() {
    modifyFile();
    std::cout << "End" << std::endl;
    return 0;
}
Wynik:
Open file
Close file
End

18 lutego 2014

[C++11] Copy control i move semantic

Kolejny zbiór nowości w standardzie, z którymi nie do końca czuje się jeszcze pewnie. Myślę, że do tematu jeszcze powrócę, jak tylko stanie się on dla mnie bardziej klarowny.

Copy control


Konstruktor kopiujący

W przypadku konstruktora kopiującego parametr jest prawie zawsze referencją na const. Rzadko zdarza się sytuacja, abyśmy byli zmuszeni do modyfikacji obiektu, z którego kopiujemy.
struct Foo {
    Foo(const Foo&);
};

Zasada trzech/pięciu/zero (the rule of three/five/zero).

Istnieją trzy (pięć - w nowym standardzie) podstawowe operacje kontrolujące kopiowanie obiektu:
  • konstruktor kopiujący
  • operator przypisania
  • destruktor
  • move konstruktor
  • operator przeniesienia (move)
Nie ma wymagań by tworzyć wszystkie z nich, jednak powinny być one traktowane jako całość. Bardzo rzadko zdarza się potrzeba stosowania tylko jednej konstrukcji z pominięciem reszty. Sam Stroustrup zaleca by przy deklaracji chociaż jednej z operacji, stworzyć wszystkie pięć. W przeciwnym razie klasa nie powinna zawierać żadnej z tych konstrukcji - "the rule of zero".
Jeżeli nie chcemy użyć któregoś z mechanizmów, poniżej wymieniono kilka zasad, którymi należy się kierować:
  • Jeśli klasa wymaga destruktora, prawie na pewno wymaga konstruktora kopiującego i operatora przypisania
  • Jeśli klasa wymaga konstruktora kopiującego, prawie na pewno wymaga operatora przypisania i vice versa
  • Klasa która nie może być kopiowana powinna definiować konstruktor kopiujący i operator przypisania jako delete, zamiast tworzyć te dwie definicje prywatne
Tworząc operator przypisania należy pamiętać o dwóch rzeczach:
  • Operator przypisania (ma to też zastosowanie dla operatora move) musi pracować poprawnie, gdy próbujemy przypisać do siebie ten same element. Ma to szczególne znaczenie jeżeli, obiekt posiada dane zapisane w pamięci dynamicznej, a w wyniku przypisania tworzymy w pamięci nowe dane i kasujemy stare. W takim przypadku dobrą praktyką jest skopiowanie wartości po prawej stronie do zmiennej lokalnej, zniszczenie wartości stojącej po lewej i w końcu przypisanie jej wartości tymczasowej. [C++Primer, s. 512; s. 536]
  • Większość operatorów przypisania współpracuje z destruktorem i konstruktorem kopiującym
Storage& operator=(const Storage& r) {
    auto tmp = new string(*r.data);
    delete data;
    data = tmp;

    return *this;
}
W przeciwieństwie do mechanizmów copy-control swap nigdy nie jest wymagany, jednakże zdefiniowanie własnej metody, może być ważną optymalizacją dla klasy, która alokuje zasoby. Operatory przypisania, które korzystają z techniki "copy and swap", są automatycznie odporne na wyjątki i obsługują samoprzypisanie.
Należy pamiętać tylko o jednej rzeczy. Nigdy nie wolno wołać bezpośrednio std::swap(). Wymusza to bowiem korzystanie z wersji bibliotecznej swap(), tymczasem klasa może mieć zmienną, która posiada własną wersję tej funkcji. Najlepiej by wyboru dokonał kompilator, przez wciągnięcie przestrzeni nazw.
inline void swap(Storage &l, Storage& r) {
    using std::swap;
    swap(l.data, r.data);
    swap(l.counter, r.counter);
}

void swap(Counter &l, Counter &r) {
    Clock *tmp = l.clock;
    l.clock = r.clock;
    r.clock = l.clock;
}
Jeżeli klasa posiada chociaż jeden z mechanizmów tj.: konstruktor kopiujący, operator przypisania lub destruktor kompilator nie stworzy nam konstruktora i operatora move!

Move semantic


Najtrudniejsza rzecz, jaka przyszła mi do opanowania wraz z nowym standardem, czyli move semantic. Kilka linków:
Ponieważ kompilatory mogą optymalizować tworzenie zmiennych tymczasowych (copy elision), które służą tylko do zainicjowania innych zmiennych wyłączyłem tą opcję.
g++ -std=c++11 -fno-elide-constructors main.cpp

std::move i std::forward

Ciekawie o nich opowiedział Scott Meyers w swoim wykładzie An Effective C++11/14 Sampler na GoingNative 2013, z czego sporo zaczerpnąłem. Żadne z mechanizmów niczego nie obiecuje, ich zadaniem jest jedynie rzutowanie (podczas kompilacji) do rvalue:
  • std::move (rzutowanie bezwarunkowe) - przekazuje obiekt jako rvalue
  • std::forward (rzutowanie warunkowe) - przekazuje obiekt jako rvalue, tylko jeżeli oryginalny obiekt był rvalue inaczej będzie to lvalue
std::move nie gwarantuje tego że coś zostanie przesunięte. Np. jego działanie na obiekcie const spowoduje jego skopiowanie. Korzystając z tego mechanizmu trzeba być bardzo ostrożnym ponieważ kompilator raczej nie poinformuje nas gdy std::move nie będzie przesuwać ("rozświetliło" by to bibliotekę standardową).

Korzystając z std::move obiecujemy, że nie będziemy korzystać już z danego obiektu chyba, że zamierzamy do niego coś przypisać lub go zniszczyć. Po wykonaniu std::move nie mamy żadnej gwarancji co do wartości przesuniętego obiektu.

Głównym zadaniem std::forward jest przekazanie argumentu (rvalue, lvalue, z const-em lub nie) w niezmienionej formie, do innej funkcji. Jeśli oryginalny obiekt, który jest wstawiony do std::forward był rvalue, zrobi z niego rvalue, a jeżeli lvalue, to lvalue - dlatego właśnie jest warunkowy.
void process(Storage& lvalue) {
    cout << "By lvalue reference" << endl;
}

void process(Storage&& rvalue) {
    cout << "By rvalue reference" << endl;
}

template <typename T>
void log(T&& param) {
    process(std::forward<T>(param));
}

int main() {
    Storage st;
    log(st);                     // przekazanie jako lvalue
    log<Storage>(std::move(st)); // przekazanie jako rvalue
}
Wynik:
Default constr.:  defaultData
By lvalue reference
By rvalue reference
Destructor:       defaultData

"Jeżeli oczekujesz szybkiego działania przekazuj przez wartość"

Zalecenia do poprzednich wersji standardu sugerowały by lvalue były opatrzone modyfikatorem const. Jest to już nieprawdą, bowiem powodowało by to kopiowanie obiektu. Jeżeli chcemy coś przesunąć, const nam w tym przeszkodzi.
void process(const Storage st) {
    Storage my = std::move(st); // Kopiowanie!
}

int main() {
    Storage st;
    process(st);
}
Wynik
Default constr.:  defaultData
Copy constructor: defaultData
Copy constructor: defaultData
Destructor:       defaultData
Destructor:       defaultData
Destructor:       defaultData

noexcept dla operacji move

Ponieważ z założenia operacje "move konstruktora" i "move operatora" kradną zasoby i nie alokują żadnych nowych, więc nie powinny też rzucać żadnych wyjątków. Powinniśmy poinformować o tym kompilator stosując noexcept. Co więcej, kontenery takie jak std::vector, sprawdzają, czy "move konstruktor" jest noexcept (podczas realokacji), jeżeli nie, zostanie zawołany zwykły konstruktor kopiujący.
#include <iostream>
#include <string>
#include <vector>

using namespace std;

struct Verbose {
    Verbose(std::string) { cout << "Constructor" << endl; }
    Verbose(const Verbose&) { cout << "Copy" << endl; }
    Verbose(Verbose &&) /*noexcept*/ {
        cout << "Move" << endl;
    }
    Verbose& operator =(const Verbose&) { cout << "assign=" << endl; }
    Verbose& operator =(Verbose&&) noexcept { cout << "move=" << endl; }
    ~Verbose() noexcept { cout << "Destructor" << endl; }
};

int main() {
    std::vector<Verbose> v;
    v.emplace_back("aaa");
    v.emplace_back("bbb");
}
Wynik:
Constructor
Constructor
Copy
Destructor
Destructor
Destructor

Uboczne działania destruktora

Move semantic niesie jeszcze jedno zwodnicze działanie, o którym należy pamiętać. Nigdy nie mamy pewności kiedy zostanie zawołany destruktor obiektu, z którego przenosimy zasoby. Jeżeli zdefiniowaliśmy własny destruktor, który ma jakieś działania uboczne, należy pamiętać o odpaleniu tego samego kodu w konstruktorze i operatorze move.

Przykłady


Pora na przykład, który pokazuje różne scenariusze, z którymi mamy do czynienia kopiując bądź przesuwając obiekty. Na początku "gadatliwa" klasa, która pokazuje, który z mechanizmów copy-control został użyty.
#include <iostream>
#include <string>
#include <vector>
#include <functional>

using namespace std;

struct Storage {
    Storage() : data("defaultData") {
        cout << "Default constr.:  " << data << endl;
    }
    Storage(std::string str) : data(str) {
        cout << "Constructor:      " << data << endl;
    }
    Storage(const Storage& st) : data(st.data) {
        cout << "Copy constructor: " << st.data << endl;
    }
    Storage(Storage&& st) noexcept : data(std::move(st.data)) {
        st.data = "old " + data + ", data was moved from here";
        cout << "Move constructor: " << data << endl;
    }
    Storage& operator=(const Storage& st) {
        if (&st != this) {
            cout << "operator= copy:   " << data << " := " << st.data << endl;
            data = "old " + data + ", now assigned " + st.data;
        } else {
            cout << "operator= copy:   " <<  data << " (this)" << endl;
        }
        return *this;
    }
    Storage& operator=(Storage&& st) noexcept {
        if (&st != this) {
            cout << "operator= move:   " << data << " := " << st.data << endl;
            string old_copy = data;
            data = std::move(st.data);
            st.data = "old " + data + ", data was moved from here";
            data = "old " + old_copy + ", now assigned " + data;
        } else {
            cout << "operator= move:   " << data << " (this)" << endl;
        }
        return *this;
    }
    ~Storage() noexcept {
        cout << "Destructor:       " << data << endl;
    }

    std::string data;
};
W przykładach simpleExample5 i simpleExample6, rzutujemy (std::move) wartość do rvalue reference - jest to referencja, więc stare dane zostają na miejscu (nic nie jest przesuwane, ani kopiowane).
void simpleExample1() {
    Storage st("val_1");
    st = st;
}

void simpleExample2() {
    Storage st1("val_1");
    Storage st2("val_2");
    st1 = st2;
}

void simpleExample3() {
    Storage st1("val_1");
    Storage st2("val_2");
    st1 = std::move(st2);
}

void simpleExample4() {
    Storage st1("val_1");
    Storage st = std::move(st1);
}

void simpleExample5() {
    Storage st1("val_1");
    Storage&& st = std::move(st1);
}

void simpleExample6() {
    Storage&& st1 = std::move(Storage("val_1"));
}

int main() {
    cout << "simpleExample1():" << endl;
    simpleExample1();
    cout << "simpleExample2():" << endl;
    simpleExample2();
    cout << "simpleExample3():" << endl;
    simpleExample3();
    cout << "simpleExample4():" << endl;
    simpleExample4();
    cout << "simpleExample5():" << endl;
    simpleExample5();
    cout << "simpleExample6():" << endl;
    simpleExample6();
}
Wynik dla simpleExample*:
simpleExample1():
Constructor:      val_1
operator= copy:   val_1 (this)
Destructor:       val_1

simpleExample2():
Constructor:      val_1
Constructor:      val_2
operator= copy:   val_1 := val_2
Destructor:       val_2
Destructor:       old val_1, now assigned val_2

simpleExample3():
Constructor:      val_1
Constructor:      val_2
operator= move:   val_1 := val_2
Destructor:       old val_2, data was moved from here
Destructor:       old val_1, now assigned val_2

simpleExample4():
Constructor:      val_1
Move constructor: val_1
Destructor:       val_1
Destructor:       old val_1, data was moved from here

simpleExample5():
Constructor:      val_1
Destructor:       val_1

simpleExample6():
Constructor:      val_1
Destructor:       val_1
W przykładzie returnValue1, kompilator sam się domyśli, że stworzona lokalnie (w ret()) zmienna zaraz zostanie zniszczona, więc zamiast korzystać z operatora przypisania (jak to było dawnej) zoptymalizuje tą operację przesuwając stworzoną zmienną lokalną. Ponieważ wyłączona jest optymalizacja "copy elision", widać dwa przesunięcia. Pierwsze przesuwa lokalną zmienną do zmiennej na stosie, drugie ze zmiennej na stosie do docelowej zmiennej st.

W drugim przypadku (returnValue2), mamy podobną sytuację, z tą różnicą, że zamiast "move operatora" działa "move konstruktor". Gdyby "copy elision" było włączone, kompilator prawdopodobnie wyciągnąłby wywołanie z tak prostej funkcji jak ret() i wywołał po prostu konstruktor w ciele naszego przykładu.
Storage ret() {
    Storage tmp("local_val");
    return tmp;
}

void returnValue1() {
    Storage st("val_1");
    st = ret();
}

void returnValue2() {
    Storage st = ret();
}

int main() {
    cout << "returnValue1():" << endl;
    returnValue1();
    cout << "returnValue2():" << endl;
    returnValue2();
}
Wyniki dla returnValue*:
returnValue1():
Constructor:      val_1
Constructor:      local_val
Move constructor: local_val
Destructor:       old local_val, data was moved from here
operator= move:   val_1 := local_val
Destructor:       old local_val, data was moved from here
Destructor:       old val_1, now assigned local_val

returnValue2():
Constructor:      local_val
Move constructor: local_val
Destructor:       old local_val, data was moved from here
Move constructor: local_val
Destructor:       old local_val, data was moved from here
Destructor:       local_val
Ostatni przykład pokazuje, że do funkcji można przekazać parametr korzystając z std::move, a więc niejako "prosząc" o skorzystanie z mechanizmu move semantic. Gdyby parametr był oznaczony modyfikatorem const, nie było by to możliwe.
Storage set(Storage st_set) {
    return st_set;
}

void settingValue1() {
    Storage st1("val_1");
    Storage st = set(st1);
}

void settingValue2() {
    Storage st = set(Storage("val_1"));
}

void settingValue3() {
    Storage st1("val_1");
    Storage st = set(std::move(st1));
}

int main() {
    cout << "settingValue1():" << endl;
    settingValue1();
    cout << "settingValue2():" << endl;
    settingValue2();
    cout << "settingValue3():" << endl;
    settingValue3();
}
Wyniki dla settingValue*.
settingValue1() - "copy elision" OFF:
Constructor:      val_1
Copy constructor: val_1
Move constructor: val_1
Move constructor: val_1
Destructor:       old val_1, data was moved from here
Destructor:       old val_1, data was moved from here
Destructor:       val_1
Destructor:       val_1

settingValue1() - "copy elision" ON:
Constructor:      val_1
Copy constructor: val_1
Move constructor: val_1
Destructor:       old val_1, data was moved from here
Destructor:       val_1
Destructor:       val_1

settingValue2():
Constructor:      val_1
Move constructor: val_1
Move constructor: val_1
Move constructor: val_1
Destructor:       old val_1, data was moved from here
Destructor:       old val_1, data was moved from here
Destructor:       old val_1, data was moved from here
Destructor:       val_1

settingValue3():
Constructor:      val_1
Move constructor: val_1
Move constructor: val_1
Move constructor: val_1
Destructor:       old val_1, data was moved from here
Destructor:       old val_1, data was moved from here
Destructor:       val_1
Destructor:       old val_1, data was moved from here
Do funkcji pass możemy przekazać tylko rvalue reference (przez rzutowanie std::move), wszelkie próby wsadzenia parametru lvalue ("jeśli zmienna ma nazwę to jest lvalue") zakończą się błędem. Taki parametr najczęściej pojawia się w metodach bibliotecznych i informuje, że możemy ukraść zasoby ze zmiennej. Towarzyszy jej bliźniacza metoda (przyjmująca const T&) będąca wersją służącą do kopiowania zasobów.
Storage pass(Storage&& st) {
    return st;
}

void passingValue1() {
    Storage st1("val_1");
    Storage st = pass(std::move(st1));
}

int main() {
    cout << "passingValue1():" << endl;
    passingValue1();
}
Wynik dla passingValue*:
passingValue1():
Constructor:      val_1
Copy constructor: val_1
Move constructor: val_1
Destructor:       old val_1, data was moved from here
Destructor:       val_1
Destructor:       val_1
Próba zdefiniowania własnego konstruktora kopiującego, operatora przypisania albo destruktora kończy się tym, że kompilator nie wygeneruje za nas operatora i konstruktora move.
struct StorageDestructor : public Storage {
    ~StorageDestructor() noexcept {
        cout << "Own destructor:   " << data << endl;
    }
};

void deriveWithDestructor1() {
    StorageDestructor st1;
    StorageDestructor st2;
    st1 = std::move(st2);
}

int main() {
    cout << "deriveWithDestructor1():" << endl;
    deriveWithDestructor1();
}
Wynik dla deriveWithDestructor1:
deriveWithDestructor1():
Default constr.:  defaultData
Default constr.:  defaultData
operator= copy:   defaultData := defaultData
Own destructor:   defaultData
Destructor:       defaultData
Own destructor:   old defaultData, now assigned defaultData
Destructor:       old defaultData, now assigned defaultData
Kolejny przykład. Choć klasa przez zdefiniowanie własnego destruktora zapobiega stworzeniu (implicit) konstruktora i operatora move, nadal mamy możliwość wymusić ich powstanie przez skorzystanie z default.
struct StorageWithDefaultMove : public Storage {
    // Destruktor zapobiega niejawnemu wygenerowaniu operatora move
    ~StorageWithDefaultMove() noexcept { }
    // Wymuszamy wygenerowania operatora move!
    StorageWithDefaultMove& operator=(StorageWithDefaultMove&&) = default;
};

void dervieWithOwnMoveAssign1() {
    StorageWithDefaultMove st1;
    StorageWithDefaultMove st2;
    st1 = std::move(st2);
}

int main() {
    cout << "dervieWithOwnMoveAssign1():" << endl;
    dervieWithOwnMoveAssign1();
}
Wynik dla dervieWithOwnMoveAssign1:
dervieWithOwnMoveAssign1():
Default constr.:  defaultData
Default constr.:  defaultData
operator= move:   defaultData := defaultData
Destructor:       old defaultData, data was moved from here
Destructor:       old defaultData, now assigned defaultData

Reference qualifier


Nowe definicje i mechanizmy wstecznej kompatybilności sprawiły, że biblioteka standardowa pozwala teraz na przypisywanie do rvalue! Czasami takie użycie może być zaskakujące. Poniższy kod jest prawidłowy.
string s1;
string s2;
s1 + s1 = "Hello world!";
Możemy się przed tym ustrzec w naszych klasach, zmuszając by operand lewostronny (obiekt, na który wskazuje this), był lvalue. Pozwala na to reference qualifier (& dla lvalue, oraz && dla rvlaue), który stosuje się tak jak const, gdy chcemy oznaczyć metody, które nie mogą modyfikować pól obiektu.

Tak jak const, kwalifikatory te można stosować dla funkcji nie statycznych, muszą też znajdować się w deklaracji i definicji, a jeżeli występują razem z const zapisuje się je na końcu. Jeżeli definiujemy dwie lub więcej metod, które mają takie same nazwy i listę parametrów, musimy wprowadzić reference qualifier dla nich wszystkich, albo dla żadnej.

Przykład. Utworzona został operator=, ale tylko dla lvalue. Dodatkowo stworzone zostały dwie metody info, wołające różny kod w zależności od tego na jakim obiekcie zostały zawołane.
#include <iostream>
using namespace std;

class Foo {
public:
    Foo operator+(const Foo &) {
        return *this;
    }
    Foo &operator=(const Foo &) & {
        return *this;
    }

    void info() const & {
        cout << "info: lvalue" << endl;
    }
    void info() && {
        cout << "info: rvalue" << endl;
    }
};

Foo retFoo() {
    return Foo();
}

int main() {
    Foo f1;
    Foo f2;
    Foo f3;

//  (f1 + f2) = f3;     // Error - zapis do rvalue, ale brak takiego operatora
//  retFoo() = f3;      // Error - zapis do rvalue, ale brak takiego operatora

    f1.info();
    retFoo().info();
}
Wynik:
info: lvalue
info: rvalue

14 lutego 2014

[C++11] rvalue reference

Przebrnięcie przez move semantic wymaga wcześniejszego zapoznania się z rvalue reference (definicja wprowadzona już w C++98). Temat sprawił mi nie lada kłopot i z pewnością do niego powrócę jak tylko będę miał lepsze zrozumienie. Na razie notatki w takiej postaci i mam nadzieje, że nie ma w nich zbyt wielu błędów. Zbiór linków:

rvalue reference


Najpierw definicja lvalue/rvalue, które znalazłem pod pierwszym linkiem:
lvalue jest wyrażeniem, które odnosi się do jakiegoś miejsca w pamięci i możemy uzyskać adres tego miejsca korzystając z operatora &. rvalue jest wyrażeniem, które nie jest lvalue.
Można też stosować definicję "if it has a name ..." - link.
Jeżeli zmienna posiada nazwę to możemy uzyskać adres tej zmiennej, dzięki operatorowi &, wtedy wiem, że jest to lvalue. Nie możemy np. uzyskać nazwy literału (liczba 42), więc wiemy, że jest to rvalue.
lvalue reference mają trwały stan, tymczasem rvalue reference są albo literałami, albo tymczasowymi obiektami utworzonymi w czasie ewaluowania wyrażenia. Mają zatem ważne właściwości [C++Primer - s.533]:
  • Odnoszą się tylko do obiektów, które zostaną zaraz zniszczone
  • Nie ma innych użytkowników danego obiektu
Możemy więc bezpiecznie ukraść zasoby, do których odnosi się rvalue reference. Poniżej kilka operacji z wykorzystaniem tego mechanizmu.
#include <iostream>

using namespace std;

int main() {
    int i = 42;         // zmienna i jest lvalue

    int& lv1 = i;       // lvalue reference - referencja na i
//  int& lv2 = i * 5;   // i*5 to rvalue (stała) nie możemy zrobić referencji która pozwala na jej modyfikację

    const int& lv3 = i * 5;  // lvalue reference - możemy, ustawić referencję na stałą

//  int&&  rv1 = i;     // rvalue reference - nie może pokazywać na lvalue
    auto&& lv4 = i;     // lvalue reference - następuje dedukcja typu na "int&"

    int&& rv2 = 42;     // rvalue reference - 42 to literał
    int&& rv3 = i * 3;  // rvalue reference - wynik mnożenia to ulotna wartość (rvalue)

//  int&& rv4 = rv2;    // rv2 nie jest ulotne i jest ważne do końca zakresu!
    int&& rv5 = std::move(rv2); // rvalue reference - obietnica, że nie będziemy korzystać z rv2!
}

Reference collapsing i dedukcja typu


Logika podpowiada, że wszystko co ma postać T&& można by nazwać rvalue reference. Niestety nie jest to prawda, zależy to bowiem od kontekstu w jakim ta postać istnieje. Zgodnie z nowym standardem wszędzie tam, gdzie mamy do czynienia z dedukcją typu (szablony, auto, typedef oraz pewne konstrukcje decltype) i jest symbol &&, czasami możemy mieć rvalue reference, a czasami lvalue reference. Scott Meyers, proponuje swoją terminologię i nazywanie takich referencji universal reference, bowiem taka referencja może pokazywać na "wszystko": lvalue, rvalue, const, non-const, ...

Kiedy parametr funkcji jest typu T&&, a dodatkowo mamy do czynienia z dedukcją typu (bo funkcja jest szablonowa), to typ może ulec zmianie. Standard przewiduje trzy wyjątki (w książce dwa).
  • Dla argumentu będącego lvalue => T będzie lvalue referencją (czyli T&)
  • Dla argumentu będącego rvalue => T będzie po prostu T - o tej zasadzie wspomina Meyers w swoim wykładzie [strona 26], nie znalazłem tego w książce.
  • Jeśli stworzymy referencję do referencji (choć nie bezpośrednio) wtedy ta referencja się "zapada". rvalue reference zapada się do rvalue reference, w pozostałych przypadkach zapadanie jest do lvalue reference.
    • T& & => T&
    • T& && => T&
    • T&& & => T&
    • T&& && => T&&
Widać te zasady na przykładach poniżej, gdy np. dla zmiennej int v1, wydedukowany zostanie typ int&. Brakuje mi tylko przykładu kiedy T&& && zapada się do T&&. Może uda mi się kiedyś coś znaleźć.

Przykłady:
#include <iostream>

// T&& to universal reference
template <typename T>
void f(T&& t, std::string name, std::string descr) {
    std::cout << descr << std::endl;
    if (std::is_const<typename std::remove_reference<decltype(t)>::type>::value)
        std::cout << name << " is const" << std::endl;
    if (std::is_lvalue_reference<decltype(t)>::value)
        std::cout << name << " is lvalue reference" << std::endl;
    if (std::is_rvalue_reference<decltype(t)>::value)
        std::cout << name << " is rvalue reference" << std::endl;
    std::cout << std::endl;
}

int main() {
    f(42, "42", "f<int>(int &&) => f<int>(int&&)");

    int v1 = 42;          // lvalue
    f(v1, "v1", "f<int&>(int& &&) => f<int&>(int&)");

    const int v2 = 42;    // const lvalue
    f(v2, "v2", "f<const int&>(const int& &&) => f<const int&>(const int&)");

    int tmp = 42;         // lvalue
    int& v3 = tmp;        // lvalue reference
    f(v3, "v3", "f<int&>(int& &&) => f<int&>(int&)");

    const int& v4 = 42;   // lvalue reference na const
    f(v4, "v4", "f<const int&>(const in& &&) => f<const int&>(const int&)");

    int&& v5 = 42;        // rvalue reference
    if (std::is_rvalue_reference<decltype(v5)>::value)
        std::cout << "in main v5 if rvalue reference" << std::endl;
    f(v5, "v5", "f<int&>(int& &&) => f<int&>(int&)");

    const int&& v6 = 42;  // rvalue reference na const
    if (std::is_rvalue_reference<decltype(v6)>::value)
        std::cout << "in main v6 if rvalue reference" << std::endl;
    f(v6, "v6", "f<const int&>(const int& &&) => f<const int&>(const int&)");

    int v7 = 67;
    f(std::move(v7), "v7", "f<int>(int &&) => f<int>(int&&)");
}
Chociaż v5 i v6 to rvalue referencje, dzięki nazwie możemy uzyskać ich adres, więc w środku funkcji f(), będą to już lvalue referencje. Aby przekazać rvalue referencję do środka, trzeba skorzystać z std::move. Wynik:
f<int>(int &&) => f<int>(int&&)
42 is rvalue reference

f<int&>(int& &&) => f<int&>(int&)
v1 is lvalue reference

f<const int&>(const int& &&) => f<const int&>(const int&)
v2 is const
v2 is lvalue reference

f<int&>(int& &&) => f<int&>(int&)
v3 is lvalue reference

f<const int&>(const in& &&) => f<const int&>(const int&)
v4 is const
v4 is lvalue reference

in main v5 if rvalue reference
f<int&>(int& &&) => f<int&>(int&)
v5 is lvalue reference

in main v6 if rvalue reference
f<const int&>(const int& &&) => f<const int&>(const int&)
v6 is const
v6 is lvalue reference

f<int>(int &&) => f<int>(int&&)
v7 is rvalue reference
Z czterech form, dla których może następować dedukcja typu, trzy zachowują się tak samo: typ szablonowy, auto oraz typedef. Niestety decltype, posiada pewne wyjątki od tej reguły, ale w tej chwili, nie chce mi się w to zagłębiać. Może kiedyś.

Value categories


W C++11 wraz z wprowadzeniem move semantic, liczba kategorii została rozbudowana:
Zależności przedstawia poniższe drzewo:
@startuml "value_categories.png"

(expression) --> (glvalue)
(glvalue) --> (lvalue)
(glvalue) --> (xvalue)

(expression) --> (rvalue)
(rvalue) --> (xvalue)
(rvalue) --> (prvalue)

@enduml

Ich właściwości można podsumować w ten sposób:
  • glvalue ("generalized" lvalue)
    • mają nazwę
  • rvalue (nazwa historyczna, bo mogły się pojawić po prawej stronie operatora przypisania)
    • można z nich przenosić
  • lvalue (nazywa historyczna, bo mogły się pojawić po lewej stronie operatora przypisania)
    • mają nazwę i można z nich przenosić
  • xvalue ("eXpiring" value)
    • mają nazwę i można z nich przenosić
  • prvalue (pure rvalue)
    • nie mają nazwy i można z nich przenosić
O ile prvalue najbardziej mi przypomina starą definicję rvalue (nie ma nazwy), to teraz w jej skład (patrz drzwo) wchodzi również xvalue. Jest kilka przykładów tego wyrażenia:
  • wywołanie funkcji, której wynikiem jest rvalue reference (np. std::move(x) zwraca rvalue reference do x)
  • a[n], wyrażenie, gdzie a jest tablicą rvalue
  • a.m, wyrażenie, gdzie a jest rvalue i m jest składową non-static typu non-reference
  • wyrażenie rzutowania do rvalue reference (np. static_cast<char&&>(x))

1 grudnia 2013

[C++11] Wyjątki i noexcept

Jeżeli wyjątek wystąpi w konstruktorze, nawet jeżeli obiekt jest tylko częściowo stworzony standard gwarantuje, że stworzone już dotąd obiekty zostaną poprawnie zniszczone. Tak samo w przypadku gdy wyjątek wystąpi w momencie inicjalizacji tablicy albo kontenera.

Jeżeli destruktor działa na operacji, która może zniszczyć obiekt, powinien on z-wrapować taką operację w blok try i obsłużyć wyjątek lokalnie. Destruktory nie powinny rzucać wyjątków! Jeżeli podczas zwijania stosu destruktor rzuci wyjątkiem którego sam nie złapał program zostanie zterminowany.
#include <iostream>
using namespace std;

struct MyClass {
    ~MyClass() /* noexcept */ {
        throw std::exception();
    }
};

int main() {
    MyClass a;
    return 0;
}
Wynik:
terminate called after throwing an instance of 'std::exception'
  what():  std::exception
The program has unexpectedly finished.
Kilka zasad dotyczących tworzenia, rzucania i łapania wyjątków:
  • Obiekty wyjątków, które są rzucane, muszą mieć dostępny destruktor, a także konstruktor kopiujący lub konstruktor move.
  • Najbardziej wyspecjalizowany catch musi pojawić się jako pierwszy.
  • Aby wyrzucić wyjątek wyżej, trzeba podać throw bez wyrażenia (throw;).
  • Aby złapać wyjątek każdego typu trzeba skorzystać z catch(...), który powinien zawsze pojawiać się jako ostatni.
#include <iostream>
#include <typeinfo>
#include <stdexcept>
using namespace std;

void old() {
    throw std::runtime_error("old()");
}

void young() {
    try {
        old();
    } catch(const std::bad_cast& e) {
        cout << e.what() << endl;
    } catch(...) {
        cout << "unknown, rethrow" << endl;
        throw;
    }
}

int main() {
    try {
        young();
    } catch(const std::runtime_error& e) {
        cout << "in main: " << e.what() << endl;
    }

    return 0;
}
Wynik:
unknown, rethrow
in main: old()

Lista inicjalizacyjna konstruktora

Konstruktor nie może złapać wyjątków rzuconych z listy inicjalizacyjnej. Aby się przed tym ustrzec, blok try musi znajdować się przed dwukropkiem. Zachowanie jest podobne w działaniu do zwykłych try-catch, z tą różnicą że złapany wyjątek zostanie ponownie wyrzucony z bloku.
#include <iostream>
#include <stdexcept>
using namespace std;

struct Member {
    Member() { }
    Member(Member& m) {
        throw std::runtime_error("Constructor");
    }
};

struct BadCalss {
    Member member;
    BadCalss(Member& m) try : member(m) {
        // body
    } catch(...) {
        cout << "some bug" << endl;
    }
};

int main() {
    Member m;
    BadCalss bc(m);
    return 0;
}
Wynik:
some bug
terminate called after throwing an instance of 'std::runtime_error'
  what():  Constructor
The program has unexpectedly finished.

noexcept

W nowym standardzie pojawił się specjalny specyfikator noexcept. Jeżeli wiemy, że funkcja nie będzie rzucać wyjątku, może z tej informacji skorzystać i programista i kompilator. Wspominał o tym również Scott Meyers w swoim wykładzie na Going Native 2013. Kompilator w takim przypadku wygeneruje znacznie mniej kodu, który będzie bardziej optymalny w działaniu.
#include <iostream>
#include <stdexcept>
using namespace std;

void fun() noexcept {
    throw std::runtime_error("fun()");
}

// gun() ma taki sam specyfikator rzucanych typów jak fun()
void gun() noexcept(noexcept(fun())) {
    cout << "gun()" << endl;
}

int main() {
    fun();
    return 0;
}
Wynik:
terminate called after throwing an instance of 'std::runtime_error'
  what():  fun()
The program has unexpectedly finished.
W rezultacie noexpect powinno być stosowane w dwóch przypadkach. Kiedy jesteśmy przekonani, że funkcja nie rzuci wyjątku albo/i gdy nie chcemy obsługiwać błędu. Jeżeli z funkcji (oznaczonej jako noexcept) zostanie jednak rzucony wyjątek, natychmiast zawołane zostanie std::terminate().

Inny przykład, w którym wyjątek przelatuje przez funkcję z noexcept, co powoduje zterminowanie programu.
#include <iostream>
#include <stdexcept>

using namespace std;


void fun_a() {
    throw std::runtime_error("bug");
}

void fun_b() noexcept {
    fun_a();
}

void fun_c() {
    try {
        fun_b();
    } catch(...) {
        cout << "Exception caught" << endl;
    }
}

int main() {
    fun_c();
}
Wynik:
terminate called after throwing an instance of 'std::runtime_error'
  what():  bug
The program has unexpectedly finished.
Niektóre kontenery jak np. std::vector, sprawdzają (podczas realokacji wektora), czy "move konstruktor" jest oznaczony jako noexcept. Jeżeli tak nie jest zostanie zwołany zwykły konstruktor kopiujący, co jest operacją dużo wolniejszą. Odpowiedni eksperyment stworzyłem w innym artykule.
Poza "move konstruktorem" noexcept standardowo powinno znaleźć się również w deklaracji destruktora.

Stare - throw()

W poprzedniej wersji standardu istniało słowo kluczowe throw(), które obecnie jest uznawane za przestarzałe. Można było za jego pomocą wyspecyfikować jakiego rodzaju wyjątki mogą być wyrzucone z funkcji, albo (odpowiednik noexpect) że funkcja nie rzuci żadnego wyjątku.
int fun(std::string& b) throw();

30 listopada 2013

[C++11] Wskaźniki na metody i pola klasy

Wskaźniki na funkcje, a co dopiero na metody klas zawsze sprawiały mi problemy. Nowy standard oferuje kilka narzędzi, dzięki którym praca z nimi staje się trochę łatwiejsza. Dużo fajnych opisów znalazłem pod poniższym linkiem.
Możemy stworzyć wskaźnik na pola lub metody, korzystając z auto bądź decltype, zamiast pisać skomplikowaną deklarację (w starym stylu). Analogicznie do operatorów odwołujących się do zmiennych obiektu (. oraz ->) istnieją dwa mechanizmy do odwołania się do pola (zmiennej lub metody) przez wskaźnik: .* oraz ->*.

Poniżej ptr_name, jest wskaźnikiem na pole MyClass::name. Dla ptr_name2 skorzystałem z auto dzięki czemu deklaracja znacznie się skróciła.
#include <iostream>
using namespace std;

struct MyClass {
    std::string name;
};

int main() {
    MyClass m;

    std::string MyClass::*ptr_name = &MyClass::name;

    m.name = "tekst";
    MyClass *d = &m;
    cout << d->*ptr_name << endl;
    cout << m.*ptr_name << endl;

    m.*ptr_name = "blow";
    cout << m.name << endl;

    auto ptr_name2 = &MyClass::name;
    m.*ptr_name2 = "blow up";
    cout << m.name << endl;

    return 0;
}
Wynik:
tekst
tekst
blow
blow up

Wskaźnik do metody w klasie

Na początek prosty przykład z wołaniem metody bezargumentowej. Standard wprowadza dodatkowe trzy mechanizmy pozwalające na odwołanie się to metody klasy. Są to std::function (klasa szablonowa), std::mem_fn oraz std::bind pozwalająca na wygenerowanie wołanego obiektu ze wskaźnika na funkcję. Ponieważ działam na metodach klasy spodziewam się, że std::mem_fn jest do tego najlepszym mechanizmem (z racji przeznaczenia), ale nie wiem, czy jest jakaś istotna różnica w porównaniu do dwóch pozostałych.
#include <algorithm>
#include <functional>
#include <iostream>

struct MyClass {
    MyClass(std::string s) : name(s) { }
    std::string info() {
        std::cout << "info " << name << std::endl;
        return name;
    }
    int fun(char*) {
        std::cout << "fun " << name << std::endl;
        return 8;
    }

    std::string name;
};

int main() {
    using namespace std::placeholders;
    std::vector<MyClass> vec {MyClass("a"), MyClass("b"), MyClass("c")};

    std::function<std::string (MyClass&)> pinfo1 = &MyClass::info;
    std::for_each(begin(vec), end(vec), pinfo1);

    std::for_each(begin(vec), end(vec), std::mem_fn(&MyClass::info));

    auto pinfo2 = std::bind(&MyClass::info, _1);
    std::for_each(begin(vec), end(vec), pinfo2);

    return 0;
}
Następny przykład to wołanie metody (MyClass::fun) posiadającej jeden argument. Na początku deklaracja wskaźnika na funkcję w starym dobrym stylu (pfun1) i jej uproszczona wersja korzystająca z auto (pfun2). Korzystanie z std::function oraz std::mem_fn jest już trochę bardziej skomplikowane i wymaga zastosowania lambdy (nie jestem pewien, czy można sobie to jeszcze jakoś bardziej ułatwić).

Najbardziej przejrzyste rozwiązanie powstało z std::bind dzięki placeholder-om. Warto pamiętać, że korzystając ze wskaźników na funkcję (tak przy deklaracji jak i wywołaniu), należy użyć dodatkowych nawiasów, ponieważ operator nawiasowy ma wyższy priorytet niż operator wskaźnika-na-pole (.*). Ogólne postać:
(Class::*fun)(param) (obj.*fun)(arg)
Pełny przykład:
#include <algorithm>
#include <functional>
#include <iostream>

struct MyClass {
    MyClass(std::string s) : name(s) { }
    std::string info() {
        std::cout << "info " << name << std::endl;
        return name;
    }
    int fun(const char* arg) {
        std::cout << "obj " << name << ", fun " << arg << std::endl;
        return 8;
    }

    std::string name;
};

int main() {
    using namespace std::placeholders;
    std::vector<MyClass> vec {MyClass("a"), MyClass("b"), MyClass("c")};

    int (MyClass::*pfun1)(const char*) = &MyClass::fun;
    std::for_each(begin(vec), end(vec), [&](MyClass& c){ (c.*pfun1)("pfun1"); });

    auto pfun2 = &MyClass::fun;
    std::for_each(begin(vec), end(vec), [&](MyClass& c){ (c.*pfun2)("pfun2"); });

    std::function<int(MyClass&, const char*)> pfun3 = &MyClass::fun;
    std::for_each(begin(vec), end(vec), [&](MyClass& c){ pfun3(c, "pfun3"); });

    std::for_each(begin(vec), end(vec),
                  [&](MyClass& c){ std::mem_fn(&MyClass::fun)(c, "std::mem_fn"); });

    std::for_each(begin(vec), end(vec), 
                  std::bind(&MyClass::fun, _1, "std::bind"));

    return 0;
}
A teraz problem, który od dawna chodził mi po głowie, czyli stworzenie funkcji hash-ującej dla unordered_map, bez wrapera w postaci free function. Ponieważ getHash() jest metodą const-ową, std::function musi to uwzględniać w parametrze.
#include <functional>
#include <iostream>
#include <unordered_map>

struct Color {
    Color(size_t v) : value(v) {}
    size_t getHash() const {
        std::cout << "getHash() " << value << std::endl;
        return value;
    }
private:
    size_t value;
};

int main() {
    using namespace std::placeholders;
    constexpr size_t bucket_count = 42;

    auto equalOp = [] (const Color& l, const Color& r)
                    { return l.getHash() == r.getHash(); };

    std::function<size_t(const Color&)> hashFun1 = &Color::getHash;
    std::unordered_map<Color,
            std::string,
            decltype(hashFun1),
            decltype(equalOp)> m1(bucket_count, hashFun1, equalOp);

    m1[Color(1)] = "blue";

    auto hashFun2 = std::mem_fn(&Color::getHash);
    std::unordered_map<Color,
            std::string,
            decltype(hashFun2),
            decltype(equalOp)> m2(bucket_count, hashFun2, equalOp);

    m2[Color(2)] = "red";

    auto hashFun3 = std::bind(&Color::getHash, _1);
    std::unordered_map<Color,
            std::string,
            decltype(hashFun3),
            decltype(equalOp)> m3(bucket_count, hashFun3, equalOp);

    m3[Color(3)] = "green";

    return 0;
}

27 listopada 2013

[C++11] virtual, final, override

Klasa, która zawiera czysto wirtualną funkcję (= 0) jest klasą abstrakcyjną (nie da się stworzyć obiektu z takiej klasy).

Często metody z klas dziedziczących po klasie abstrakcyjnej zaczynały się od słowa virtual, choć nie było to konieczne. Była to konwencja stosowana przez programistów, aby oznaczyć metody, narzucone przez interfejs.
#include <iostream>
using namespace std;

struct Interface {
    virtual void show() = 0;
    virtual void show2() {
        cout << "Interface" << endl;
    }
};

struct Father : public Interface {
    void show() {    // nie ma virtual i działa
        cout << "Father" << endl;
    }
};

struct Child : public Father {
    void show() {    // nie ma virtual i działa
        cout << "Child" << endl;
    }
};

int main() {
    Child c;
    c.show(); // "Child"
    return 0;
}
Nowa wersja języka, bardzo tą kwestię ułatwia. Aby mieć pewność, że przesłaniamy metodę z klasy bazowej nowy standard wprowadził słowo kluczowe override (za nazwą metody). Przesłonięcie będzie działać i dla tej klasy i dla wszystkich z niej dziedziczących. Kompilator ostrzeże nas, jeżeli popełniliśmy jakiś błąd i zamiast przesłonić stworzyliśmy coś nowego.
#include <iostream>
using namespace std;

struct Interface {
    virtual void show() = 0;
    virtual ~Interface() noexcept { }
};

struct MyClass : public Interface {
    void show() override {
        cout << "MyClass" << endl;
    }
};

int main() {
    MyClass c;
    c.show();
    return 0;
}
Choć jest możliwa inicjalizacja zmiennych klasy bazowej w klasie pochodnej, należy trzymać się zasady:
Każda klasa w momencie konstrukcji, powinna inicjalizować tylko swoje zmienne składowe.

Czasami tworzymy klasy, z których nie chcemy by ktoś inny dziedziczył, albo nie wiemy czy nadaje się ona na klasę bazową. W nowym standardzie możemy oznaczyć taką klasę słówkiem final - dziedziczenie zostanie zabronione.
struct NoDerived final {
};

struct Inher1 : NoDerived { // ERROR
};

struct Derived {
};

struct Base final : public Derived { // To jest OK
};
Przesłanianie metod (czyli tylko wirtualnych), oznaczonych jako final, zakończy się error-em.
#include <iostream>
using namespace std;

struct Interface {
    virtual void show() = 0;
};

struct Base : public Interface {
    void show() final {
        cout << "Base" << endl;
    }
};

struct Derived : public Base {
    void show() override {     // ERROR!
        cout << "Derived" << endl;
    }
};

24 listopada 2013

[C++11] Budowaniu szablonów

Szablony i ich specjalizacje powinny być deklarowane w tym samym pliku nagłówkowym. Na początku powinny wystąpić wszystkie szablony ogólne, a następnie ich specjalizacje. Możemy częściowo wyspecjalizować tylko klasę. Nie można częściowo wyspecjalizować funkcji szablonowej [1].

Wewnątrz klasy szablonowej kompilator traktuje referencję do tego samego szablonu tak jakby był podany argument dla tego szablonu.
#include <iostream>
using namespace std;

template <typename T>
struct Single {
    Single& me_one() {
        return *this;
    }
    Single<T>& me_two() {
        return *this;
    }
    std::string show() {
        return "chain call";
    }
};

int main() {
    Single<int> s;
    cout << s.me_one().me_two().me_two().show() << endl;
    return 0;
}
Wynik:
chain call
W nowym standardzie, możemy uczynić typ parametryczny przyjacielem klasy.
#include <iostream>
using namespace std;

template <typename Type>
struct Bar {
    friend Type;
protected:
    std::string show() {
        return "protected show()";
    }
};

struct FriendType {
    void run(Bar<FriendType>& b) {
        cout << b.show() << endl;
    }
};

int main() {
    FriendType fry;
    Bar<FriendType> bar;
    fry.run(bar);
    return 0;
}
Wynik:
protected show()
Kompilator domyślnie zakłada że nazwa do której się odwołujemy przez operator zakresu (namespace) nigdy nie jest typem dlatego w takich przypadkach należy używać słowa typename.
template <typename T>
typename T::value_type fun(const T& c) {
    return c.res();
}
Wczesne wersje standardu dopuszczały domyślne parametry tylko dla szablonów klasy, w nowym standardzie, można stosować je także dla funkcji.
#include <iostream>
using namespace std;

template <typename T, typename F = less<T> >
int cmp(const T& a, const T&b) {
    F func = F();
    return func(a, b);
}

int main() {
    cout << cmp(1, 3) << endl;
    cout << cmp<int, greater<int>>(1, 3) << endl;
    return 0;
}
Wynik:
1
0
W dużych systemach, koszt inicjowania tego samego szablonu w wielu plikach, może być bardzo duży. Nowy standard pozwala uniknąć tego narzutu dzięki "explicit instantiation" ~ jednoznaczna konkretyzacja. Kiedy kompilator napotka deklarację szablonu jako extern, nie wygeneruje kodu dla tej konkretyzacji w danym pliku - taka deklaracja obiecuje, że gdzieś w programie będzie użyta non-extern konkretyzacja. Może być kilka deklaracji typu extern, ale zawsze musi być jedna definicja dla tej konkretyzacji.
// kompilator nie wygeneruje kodu w tym pliku
extern template class Blob<string>;
// kod tej szablonowej metody zostanie wygenerowany w obecnym pliku .o
template int compare(const int&, const int&);

17 listopada 2013

[C++11] Przeciążenia operatorów

Zbiór notatek które zebrałem studiując tematykę operatorów po lekturze "C++ Primer". Najczęściej przeciążanym (overload) operatorem, jest chyba operator nawiasowy () który stosuje się w celu tworzenia funktorów. C++11 sprawi, że w jego miejsce częściej będzie pojawiać się lambda. W wielu przypadkach nadal, warto korzystać z obiektów funkcyjnych dostarczonych wraz z biblioteką standardową (pozytywnie wypowiadał się na ten temat także Stephan T. Lavavej na GoingNative 2013 - Don’t Help the Compiler). Nie tylko potrafią być krótsze od lambd, ale potrafią też poprawnie pracować ze wskaźnikami.

Kiedy tego nie robić?

Przeciążenie (overload) operatora ma tylko sens, jeżeli programista nie będzie zaskoczony jego działaniem.
Standard nie gwarantuje w jakiej kolejności będą wołane operandy. Np. dla f() + g(), nie wiemy, która z funkcji zostanie zawołana jako pierwsza. Mamy za to pewność że priorytety operatorów będą zachowane (czyli np. mnożenie będzie przed dodawaniem) [C++Prime, s. 138].
Nie należy przeciążać operatora kropki (.), pobrania adresu (&), logicznego AND (&&) oraz logicznego OR (||).
#include <iostream>

using namespace std;

struct Num {
    Num(int v) : value(v) { }
    Num() : Num(0) { }

    int value;
};

Num operator+(const Num& first, const Num& second) {
    return Num(first.value + second.value);
}

Num operator*(const Num& first, const Num& second) {
    return Num(first.value * second.value);
}

int main()
{
    Num a(2);
    Num b(2);
    Num c(2);

    cout << (a + b).value << endl;
    cout << (a * b).value << endl;
    cout << (a * b + c).value << endl;
    cout << (a + b * c).value << endl;
}
Wynik:
4
4
6
6

Gdzie tworzyć deklaracje?

Kiedy już decydujemy się na przeciążenie operatora, stajemy przed dylematem, czy stworzyć go jako element składowy klasy, czy jako funkcję poza klasą. Pomocne mogą być poniższe wskazówki:
  • operatora przypisania (=), nawisu kwadratowego ([]), zawołania (()) oraz strzałki (->) muszą być zdefiniowane jako składniki klasy
  • operatory mieszane z przypisaniem (+=, -=, *=, /= itp.) generalnie powinny być składowymi klasy, ale w przeciwieństwie do samego operatora przypisania (=) nie jest to wymagane
  • operatory które zmieniają stan obiektu, albo są z nim ściśle powiązane np. inkrementacja (++), dekrementacja (--), dereferencja (*) zazwyczaj powinny być składowymi klasy
  • operatory symetryczne (takie które mogą konwertować którykolwiek z operandów) tj. arytmetyczne (+, -), równości (==), relacji (<, >, <=, <=) i bitowe (|, &, ^, <<, >>), zazwyczaj powinny być definiowane jako funkcje poza klasą

Szczegóły dla konkretnych operatorów

Operatory I/O - wejścia, wyjścia - (<<, >>), powinny być funkcjami poza klasą i powinny drukować zawartość z bardzo minimalnym formatowaniem. Jako, że czasami muszą mieć dostęp do niepublicznych danych, powinny być zadeklarowane jako przyjaciele (friend) klasy. Operator wejściowy powinien radzić sobie z sytuacją, gdy "wejście" zawiedzie, operator wyjściowy generalnie się tym nie przejmuje.

Dla operatora nawiasu kwadratowego ([]) warto zrobić wersję const oraz non-const

Dla operatorów inkrementacji (++) i dekrementacji (--) istnieją dwie wersje: prefiksowa i postfiksowa. Wersja postfiksowa dla rozróżnienia bierze jako argument dodatkowy nieużywany parametr typu int, kompilator wstawia tam zero.
#include <iostream>
using namespace std;

struct Counter {
    double d;
    Counter() : d(0.12) { }
    Counter operator++() {
        d += 1.0;
        cout << "prefix:  ++obj" << endl;
        return *this;
    }

    Counter operator++(int) {
        Counter tmp = *this;
        d += 1.0;
        cout << "postfix: obj++" << endl;
        return tmp;
    }
};

int main() {
    Counter c;
    c++;
    ++c;
}
Wynik:
postfix: obj++
prefix:  ++obj

Konwersje typów

Konwersje z jednego typu na drugi mogą być bardzo mylące. Czasami lepiej stworzyć odpowiednią metodą, która lepiej poinformuje nas o intencji danej konwersji. Operatory konwersji powinny być składowymi klasy, nie powinny określić typu zwracanego i posiadać pustą listą parametrów. Funkcja zazwyczaj powinna być const-owa.

We wcześniejszej wersji języka konwersja na typ bool była problematyczna, ponieważ bool jest typem arytmetycznym. Obiekt klasy która pozwala na konwersję na bool, mógł zostać użyty we wszystkich miejscach, gdzie wymagany jest typ arytmetyczny. Gdyby std::cin dało się konwertować na typ bool, możliwa była by operacja "std::cin << 34" - w rezultacie doszło by do przesunięcia bitowego (bool zostałby jeszcze skonwertowany na int).

Nowy standard wprowadza konwersje explicit. Powinna się ona ograniczyć raczej tylko do bool i zazwyczaj z intencją sprawdzania wyniku w jakimś warunku.
Jeżeli konwersja jest explicit, możemy jej używać ale tylko z rzutowaniem. Wyjątkiem jest niejawne (implicit) korzystanie z konwersji dla:
  • warunków w if, while oraz do-while
  • warunków w for
  • operatorów dla logicznego NOT (!), OR (||) oraz AND (&&)
  • warunków dla ?:
#include <iostream>
using namespace std;

struct SmallInt {
    double val;
    SmallInt(double v) : val(v) { }

    explicit operator int() {
        return val;
    }

    explicit operator bool() {
        return val > 3.0;
    }
};

int main() {
    SmallInt si(3.14);
//  cout << si + 1 << endl; // ERROR wymagana jest niejawna konwersja,
                            // ale operator rzutowania na int jest explicit
    cout << static_cast<int>(si) + 1 << endl;

    if (si)
        cout << "Bigger than PI" << endl;

    return 0;
}
Wynik:
4
Bigger than PI