30 czerwca 2013

C++11 unordered containers

Nowy zestaw kontenerów asocjacyjnych (unordered_set, unordered_multiset, unordered_map i unordered_multimap), do porównywania używają funkcji hash-ujących i operatora == dla klucza. Są najbardziej użyteczne, kiedy korzystamy z klucza, dla którego nie ma oczywistej relacji porządku. A także w aplikacjach, gdzie koszt przechowywania elementów w porządku nie jest istotny. Zazwyczaj jednak łatwiej i wydajniej jest korzystać ze zwykłych kontenerów asocjacyjnych.

Kontenery (unordered_*) wkładają wszystkie elementy z takim samym hashem do tego samego kubełka. Wydajność działania zależy od funkcji hash-ującej, która powinna zawsze zwracać ten sam rezultat, dla tych samych argumentów. Idealnie, każda wartość, powinna trafić do innego kubełka. W przypadku wyszukiwania, najpierw odszukiwany jest kubełek, a później sekwencyjnie porównywane są jego elementy.

Biblioteka dostarcza, kilka metod, które pokazują w jaki sposób zarządzane są nasze obiekty i w ten sposób, pozwalają dowiedzieć się gdzie wydajność może zostać poprawiona.
#include <iostream>
#include <unordered_map>

using namespace std;

int main()
{
    const std::string key = "kilo";
    std::unordered_map<std::string, int> m = {
        {"delta", 7},
        {key, 99},
        {"hotel", 21},
        {"foxtrot", 2},
    };

    cout << "Ilosc kubelkow: "     << m.bucket_count() << endl;
    cout << "Max do skorzytania: " << m.max_bucket_count() << endl;

    const int bucket = m.bucket(key);
    cout << "Element '" << key << "' jest w kubelku: " << bucket << endl;
    cout << "Elementow w kupelku(" << bucket << "): " << m.bucket_size(bucket) << endl;

    cout << "Srednia liczba elem. na kubelek: " << m.load_factor() << endl;
    cout << "Sredni rozmiar kubelka jaki kontener bedzie chcial zachowac: " <<
            m.max_load_factor() << endl;

    for(const auto &val: m)
        cout << "map[" << val.first << "] = " << val.second << endl;

    return 0;
}
Dodatkowo mamy do dyspozycji metody rehash(n), która reorganizuje mapę tak, by liczba_kubełków >= n i liczba_kubełków > size/max_load_factor. Oraz metodę reserve(m), która sprawia, że mapa może przechowywać m elementów bez rehash-owania.
Wyniki:
Ilosc kubelkow: 11
Max do skorzytania: 268435455

Element 'kilo' jest w kubelku: 6
Elementow w kupelku(6): 1

Srednia liczba elem. na kubelek: 0.363636
Sredni rozmiar kubelka jaki kontener bedzie chcial zachowac: 1

map[foxtrot] = 2
map[hotel] = 21
map[kilo] = 99
map[delta] = 7
Domyślnie tego typu kontenery korzystają z operator ==, by porównać elementy, oraz z obiektu typu hash<key_type> by wygenerować hash. Można jednak wskazać własne metody, które się tym zajmą.
#include <iostream>
#include <unordered_map>

using namespace std;

struct Color {
    Color(int v) : value(v) {}
    string getValue() const {
        return "#" + std::to_string(value);
    }

private:
    int value;
};

size_t hashColor(const Color& c) {
    cout << "hashColor " << c.getValue() << endl;
    return c.getValue().length();
}

bool equalOp(const Color &l, const Color &r) {
    cout << "equalOp " << l.getValue() << " " << r.getValue() << endl;
    return l.getValue() == r.getValue();
}

int main()
{
    std::unordered_map<Color,
                       string,
                       decltype(hashColor)*,
                       decltype(equalOp)*> m(42, hashColor, equalOp);

    m[Color(1)] = "red";
    m[Color(2)] = "blue";
    m[Color(3)] = "green";

    return 0;
}
Wyniki:
hashColor #1
hashColor #2
equalOp #2 #1
hashColor #3
equalOp #3 #2
equalOp #3 #1
Na koniec link, do artykułu wgryzającego się w szczegóły implementacyjne unordered_map:

27 czerwca 2013

[C++] Trochę o wirtualności (kiedy stosować wirtualne destruktory) i metodach szablonowa

Szukając informacji na temat stosowania destruktorów wirtualnych natknąłem się na artykuł (http://www.gotw.ca/publications/mill18.htm) Herba Suttera, dotyczący stosowania tego mechanizmu i troszkę więcej. Poniżej moje notatki.

Metody wirtualne bardzo rzadko powinny być publiczne (jeśli w ogóle). Publiczna wirtualna metoda, robi dwie rzeczy: specyfikuje interfejs i pokazuje detale implementacyjne - pozwalające na zmianę zachowania. Dobrze by było rozdzielić te dwie rzeczy. Sutter zaleca stosowania NVI (Non-virtual interface - wersja wzorca projektowego "metoda szblonowa" - definiuje szkielet algorytmu, który korzysta m.in. z operacji pierwotnych przedefiniowanych przez klasy pochodne). Korzyści: Interfejs staje się stabilny, a wszystkie modyfikacje oddelegowane zostają do klas pochodnych.
  • Porada #1: Preferuj tworzenie interfejsu jako nonvirutal, korzystająć z wzorca projektowego "metoda szablonowa"
  • Porada #2: Preferuj tworzenie metod wirtualnych jako prywatne. Metoda wirtualne stosuje się tylko by umożliwić modyfikacje działania.
  • Porada #3: Jeżeli klasa pochodna, potrzebuje wykonać metodę z klasy bazowej tylko wtedy może być zmieniona na protected.
Klasa powinna mieć wirtualny destruktor, jeżeli zamierzamy niszczyć (polimorficznie) obiekt korzystając ze wskaźnika na klasę bazową.
  • Porada #4: Destruktor klasy bazowej powinien być publiczny i wirtualny, albo protected i niewirtualny (gdy nie stosujemy polimorfizmu).
  • O ile stosowanie publicznego wirtualnego destruktor do mnie jeszcze przemawia, to nie jestem w stanie w tej chwili znaleźć zastosowania dla protected nonvirtual.
  • Porada #5: Nie dziedzicz po klasach konkretnych - pozostaw klasy nie będące liśćmi jako abstrakcyjne.
Oczywiście nietrudno znaleźć głosy, które nie do końca się z tym zgadzają. C++FAQ [23.4], zaleca stosowanie chronionych (protected) metod wirtualnych, ponieważ dla wielu deweloperów jest zaskoczeniem to, że prywatne wirtualne metody mogą zostać nadpisane. Marshall Cline, podaje też, dwa przypadki (C++FAQ [23.3]), dla których jego zdaniem wirtualne metody powinny być publiczne.

Pierwsza jest odwróceniem, metody szablonowej tj. chcemy by główny algorytm, mógł być modyfikowany przez klasy pochodne, natomiast prymitywne metody pozostały stałe w klasie bazowej - nie muszą być one wirtualne, bo i tak klasa pochodna zdecyduje, czy będzie chciała z nich skorzystać, czy z czegoś innego (C++FAQ [23.2]).

Ze zrozumieniem drugiej (C++FAQ [23.9]) mam niestety problem.

23 czerwca 2013

C++11 std::bind

Poprzednie wersje std::bind1st, std::bind2nd, z powodu swoich ograniczeń w nowym standardzie zostały uznane za przestarzałe i w przyszłości mogą być usunięte. Nowa wersja czerpie dużo z boost::bind. Poniżej ogólna postać tej funkcji:
auto nowFunkcja = std::bind(funkcja, argumenty);
std::bind dobrze współgra z lambdami, a czasami można je stosować zamiennie. Jeżeli chcemy przekazać jakiś parametr jako referencję trzeba posłużyć się funkcją std::ref. Przykład:
#include <iostream>
#include <vector>
#include <algorithm>

void in_range(std::ostream &os, int top, int bottom, int value) {
    if (value > bottom and value < top)
        os << "Value: " << value << std::endl;
}

int main()
{
    std::vector<int> vec {1, 3, 2, 6, 2};
    int const myBottom = 2;
    int const myTop = 9;

    using namespace std::placeholders;
    auto fun = std::bind(in_range, std::ref(std::cout), myTop, myBottom, _1);
    std::for_each(begin(vec), end(vec), fun);

    return 0;
}
std::for_each karmi naszą funkcję (fun) jednym argumentem przechwytywanym przez std::placeholders::_1. Wynik:
Value: 3
Value: 6

22 czerwca 2013

C++11 lambda

Funkcje lambda (nie posiadające nazwy) doczekały się swojej wersji w najnowszym standardzie [1]. Ich ogólna postać wygląda następująco:
[capture list] (parameter list) -> return type { function body }
  • caputre list - najczęściej pusta, zawiera listę lokalnych zmiennych
  • parameter list - można ominąć. Wygląda tak jak w przypadku zwykłych funkcji. Nie ma tu czegoś takiego jak argumenty domyślne
  • return type - można ominąć. Wygląda tak jak w przypadku zwykłych funkcji, jednak cała deklaracja lambda musi korzystać z konstrukcji "trailing return"
  • function body - wygląda tak jak w przypadku zwykłych funkcji
Lambda może używać lokalnych zmiennych pod warunkiem, że zostały zawarte w "capture list". Inne zmienne np. statyczne lub zdefiniowane poza główną funkcją, są normalnie dostępne. To jakie zmienne i w jaki sposób będą dostępne, informuje poniższa notacja.
  • [ ] - lista pusta, lambda nie może używać zmiennych z głównej funkcji
  • [name1, name2] - nazwy zmiennych oddzielone przecinkami, które będą wykorzystywane w lambdzie (domyślnie wartości są kopiowane), jeżeli jakaś nazwa zostanie poprzedzona &, lambda dostanie ją przez referencję
  • [&] - przed domniemanie (implicit), wszystkie zmienne z głównej funkcji będą dostępne przez referencję
  • [=] - przed domniemanie (implicit), wszystkie zmienne z głównej funkcji będą dostępne przez kopię. Jeżeli lambda jest wewnątrz metody to od C++20 jest to przestarzały zapis i powinien być zastąpiony przez [=, this]
  • [&, id1, id2] - zmienne id1, id2 będą dostępne przez kopię (nie poprzedzać żadnej &!!!), wszystkie inne będą domyślnie dostępne przez referencję
  • [=, &ref1, &ref2] - zmienne ref1 i ref2, będą dostępne przez referencję, wszystkie inne będą domyślnie dostępne przez kopię
  • [this], [&, this], [=, this], [&] - this (od C++17) będzie dostępne przez referencję.
  • [*this], [&, *this], [=, *this] - this (od C++17) będzie dostępne przez kopię
Pierwszy przykład wykorzystania lambdy w algorytmie std::count_if.
#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

int main()
{
    vector<int> vec = {1, 4, 7, 2, 3};
    int evens = std::count_if(begin(vec), end(vec),
                              [](int value) { return value % 2 == 0; });
    cout << "Parzystych: " << evens << endl;

    return 0;
}
Wynik:
Parzystych: 2
Drugi przykład trochę bardziej rozbudowany. Zliczane są elementy powyżej pewnego progu (threshold), a wynik zapisywany jest do zmiennej znajdującej się w głównej funkcji.
#include <iostream>
#include <vector>
#include <algorithm>

int main()
{
    std::vector<int> vec = {1, 4, 7, 2, 3};
    int count_above = 0;
    int threshold = 3;
    std::for_each(begin(vec), end(vec),
                  [&, threshold](int &v) { if(v > threshold) count_above++; });

    std::cout << "Values above(" << threshold << "): " << count_above << std::endl;

    return 0;
}
Wynik:
Values above(3): 2
Jeżeli chcemy zmienić wartość przechwyconej zmiennej musimy po "parameter list" dodać słowo kluczowe mutable. Ale wartość ta będzie zmieniona tylko w obrębie samej lambdy! Poniżej link, z dobrym wyjaśnieniem tego mechanizmu:
#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

int main()
{
    vector<int> vec = {1, 2, 3, 4};
    int count = 1;
    auto change_second = [=](int v) mutable {
        if (count == 2)
            v = 222;
        count++;
        return v;
    };

    cout << "count berfore: " << count << endl;
    std::transform(begin(vec), end(vec), begin(vec), change_second);
    cout << "count after:   " << count << endl;

    cout << "New vector values: ";
    for(int& v : vec)
        cout << v << " ";

    return 0;
}
Wynik (jak się zaczyna zabawę z lambdami, trochę zaskakujący):
count berfore: 1
count after:   1
New vector values: 1 222 3 4 
Standard nie definiuje w jaki sposób funkcja lambda zostanie zaimplementowana. Ta sama lambda, w dwóch implementacjach może być dwoma zupełnie innymi typami. Do przechowywania można wykorzystać std::function, wskaźnik do funkcji lub wykorzystać detekcję tupu za pomocą auto. I właśnie korzystanie z auto jest zalecanym sposobem. std::function, może wołać lambdę, za pośrednictwem funkcji wirtualnych, co wpływa na szybkość działania programu (mechanizm type-erasure - nie weryfikowałem tego).

To czego zabrakło w C++11, a zostało naprawione w C++17, czyli obsługa this.
#include <iostream>
#include <vector>

using namespace std;

struct MyClass {
    MyClass() : vec{0, 0, 0} {
    }
    void update() {
        auto fun = [&, this]() { this->vec[1] = 9; };
        fun();
    }

    vector<int> vec;
};

int main()
{
    MyClass obj;
    obj.update();

    for (const auto& v : obj.vec)
        cout << v << " ";
}
Wynik:
0 9 0
Oczywiście sprawa się na tym nie kończy. Istnieją duża bardziej skomplikowane przypadki np. zwracanie lambdy modyfikująca pola obiektu z metody tego obiektu. Dobry opis na tym blogu [2].

2 czerwca 2013

Ubuntu 13.04 - powolna praca na VirtualBox-ie

Nowy system, nowe problemy, tym razem z Unity, które od wersji 12.10 występuje jedynie w wersji 3D. Cały system zaraz po uruchomieniu wydaj się działać niezwykle wolno. Oznacza to tyle, że na VirtualBoz-ie potrzebne są dodatkowe zabiegi, aby dało się "płynnie" pracować. Poniżej link, który opisuje całą procedurę:

http://askubuntu.com/questions/207813/why-does-an-ubuntu-12-10-guest-in-virtualbox-run-very-very-slowly

1 czerwca 2013

C++11 - konwersja z liczby na string i odwrotnie

Nowy standard wnosi kilka nowych metod, umożliwiających konwertowanie string-u na liczbę i odwrotnie. W zależności od tego na jaki typ liczbowy trzeba dokonać konwersji, mamy do dyspozycji kilka metod: stoi() (string na unsigned int), stol() (na typ long), stoll(), stod() itd. Są to odpowiedniki metod std::ato* (np. std::atoi()), które przyjmowały jako argument wskaźnik do tablicy znaków.

Inny rodzaj konwersji dostarcza std::to_string(), który zapisuje liczbę w postaci stringu (nie ma flag formatujących). Więcej informacji: http://en.cppreference.com/w/cpp/string/basic_string/to_string.

Co ciekawe, do standardu C++ została dołożona z C także funkcja std::snprintf(), nie byłem świadomy, że jej jeszcze tam nie było. Więcej info: http://en.cppreference.com/w/cpp/io/c/fprintf.
#include <iostream>
#include <string>
#include <sstream>
#include <cstdio>

int main()
{
    const int i = std::stoi("567");
    std::cout << "int: " << i << std::endl;

    const std::string s = std::to_string(783);
    std::cout << "string: " << s << std::endl;

    const double d = 9.8765E4;
    constexpr int size_tab = 100;
    char tab[size_tab];
    std::snprintf(tab, size_tab, "%e", d);
    const std::string stab(tab);
    std::cout << "double: " << stab << std::endl;

    std::stringstream strstream;
    strstream << "double: " << std::scientific << d << std::endl;
    std::cout << strstream.str();

    return 0;
}
Wynik.
int: 567
string: 783
double: 9.876500e+04
double: 9.876500e+04