31 maja 2013

C++11 - zarządzanie pamięcią pod nowe obiekty w kontenerach sekwencyjnych

W nowym standardzie kontenery sekwencyjne zyskały nowe metody, które lepiej pozwalają nam kontrolować pamięć pod nowo tworzone obiekty. Wykaz wszystkich dostępnych można znaleźć pod adresem:
Wśród nich jest m.in. shrint_to_fit(). W programie poniżej, przedstawiono jego porównanie z już istniejącymi metodami.
  • resize() - zwiększa lub zmniejsza ilość elementów w kontenerze. Jeżeli trzeba dołożyć nowych elementów zostaną one zainicjowane domyślną wartością
  • reserve() - nie tworzy nowych elementów. Jego zadaniem jest zwiększenie miejsca pod nowo tworzone elementy
  • capacity() - podaje ile miejsca zostało przeznaczone na wszystkie elementy
  • shrint_to_fit() - zazwyczaj ilość prealokowanej pamięci jest większa niż liczba elementów w wektorze, za pomocą tej nowej metody możemy zwolnić ekstra pamięć

#include <iostream>
#include <string>
#include <vector>

using namespace std;

void status(const string& func_name, const vector<int>& v) {
    cout << func_name << "size: " << v.size() << ", capacity: " << v.capacity() << endl;
}

int main()
{
    vector<int> v;

    for(int i = 0; i < 3; ++i)
        v.push_back(1);

    status("push_back()     ", v);
    v.resize(5);
    status("resize()        ", v);
    v.shrink_to_fit();
    status("shrint_to_fit() ", v);
    v.reserve(10);
    status("reserve()       ", v);
    v.reserve(3);
    status("reserve()       ", v);

    return 0;
}
Wynik:
push_back()     size: 3, capacity: 4
resize()        size: 5, capacity: 6
shrint_to_fit() size: 5, capacity: 5
reserve()       size: 5, capacity: 10
reserve()       size: 5, capacity: 10
Inny przykład to metoda emplace() (jest też w innych wariantach: emplace_back(), emplace_front()). Jej zadaniem jest stworzenie obiektu w miejscu.
#include <iostream>
#include <vector>

using namespace std;

struct Obj {
    int value;
    Obj(int v) : value(v) {
        cout << "Call constructor, value = " << value << endl;
    }
    Obj(const Obj& o) : value(o.value) {
        cout << "Call copy constructor, value = " << value << endl;
    }
    Obj(const Obj&& o) noexcept : value(std::move(o.value)) {
        cout << "Call move constructor, value = " << value << endl;
    }
    Obj& operator=(const Obj& o) = delete;
};

int main()
{
    vector<Obj> vobj;
    Obj o(111);

    cout << "Testing emplace_back():" << endl;
    vobj.emplace_back(222);

    cout << "Testing push_back():" << endl;
    vobj.push_back(o);

    return 0;
}
Wynik:
Call constructor, value = 111

Testing emplace_back():
Call constructor, value = 222

Testing push_back():
Call copy constructor, value = 111
Call copy constructor, value = 222

26 maja 2013

Bezpieczeństwo w sieciach GSM

Dwie ciekawe prezentacje omawiające bezpieczeństwo w sieciach GSM.

Pierwsza: "Blackhat 2011 - War Texting: Identifying and Interacting with Devices on the Telephone", w której autor szeroko omawia ataki na technologię M2M (machine-to-machine), gdzie urządzenia komunikują się ze sobą za pomocą wiadomości tekstowych w sieciach GSM. Jednym z przykładów może być usługa oferowana przez producentów samochodów, pozwalająca na otwarcie/uruchomienie samochodu, w przypadku, gdy klient zapodział, gdzieś kluczyk. Na końcu prezentacja ataku na taką usługę.

Druga prezentacja: "Blackhat 2010 - Attacking phone privacy" autorstwa Karsten Nohl, pokazująca skuteczny (i tani) atak na algorytm A5/1 (http://pl.wikipedia.org/wiki/A5_(kryptografia)) stosowany w sieciach GSM, do zapewnienia poufności informacji. Również z prezentacją na końcu.

11 maja 2013

C++11 lista jednokierunkowa - std::forward_list

Nowy standard dostarczył nam jedną z podstawowych struktur danych, czyli listę jednokierunkową. Przydatne linki:
  • http://www.cplusplus.com/reference/forward_list/forward_list/
  • http://en.cppreference.com/w/cpp/container/forward_list
W założeniu taka struktura ma umożliwiać szybkie wstawianie i usuwanie elementów, zajmuje też mniej pamięci niż standardowa lista (tylko jeden iterator - na element następny). Ciekawe jak by wypadła w konfrontacji z wektorem. Może kiedyś przeprowadzę test.
#include <iostream>
#include <forward_list>

int main()
{
    std::forward_list<int> f {3, 4, 5, 1, 2, 7, 9};

    std::forward_list<int>::iterator pos = f.before_begin();
    f.insert_after(pos, 404);

    for (std::forward_list<int>::iterator it = f.begin(); it != f.end(); ++it)
        std::cout << *it << " ";
    return 0;
}
Wyniki:
404 3 4 5 1 2 7 9
Lista jednokierunkowa posiada specjalne wersje metod (insert_after(), emplace_after(), erase_after()), operujących na iteratorach. Według konwencji np. insert(), powinno wstawiać element, przed danym iteratorem, ponieważ w liście jednokierunkowej jest to niemożliwe (bo nie mamy dostępu do elementu poprzedniego by w nim poprawić iterator next) biblioteka dostarcza metody insert_after().

10 maja 2013

std::array (nowy kontener w C++11)

Nowy standard wprowadza nowe kontenery jednym z nich jest std::array. Odpowiednik tablicy, musi zostać zainicjowany w momencie stworzenia. Obowiązkową wartością przekazywaną do kontenera jest jej rozmiar. Jeżeli będzie mniejszy niż liczba elementów, pozostałe zostaną zainicjowane domyślnymi wartościami. Po stworzeniu, można się dobrać do konkretnych elementów.
#include <iostream>
#include <array>

int main()
{
    std::array<int, 6> arr {1, 5, 7, 7, 2};
//  arr = {1, 1, 2, 2, 3, 3,};  // Error

//  arr[77534905] = 4;          // Segmentation fault - zakres nie jest sprawdzany
    arr.at(3) = 404;            // W razie błędu zwróci std::out_of_range
    for(const int& i : arr)
        std::cout << i << " ";
    return 0;
}
Z racji przeznaczenia, kontener nie ma kilku charakterystycznych dla innych kontenerów metod np. "clear". Przydatny link http://en.cppreference.com/w/cpp/container/array.
Wyniki:
1 5 7 404 2 0

9 maja 2013

Google I/O 2009 - The Myth of the Genius Programmer

Ciekawa prezentacja na Google I/O (http://www.youtube.com/watch?v=0SARbwvhupQ) - "The Myth of the Genius Programmer". Właściwie rozprawa bardziej psychologiczna, ale skoro wiedzę o szczęściu czerpiemy z filmów, to dlaczego, o życiu nie mamy się dowiedzieć z internetu.

Kilka wniosków, które sobie zanotowałem:
  • Geniusze jest niezwykle rzadki, ale są w nas naturalne instynkty, które sprawiają, że chcemy się nim stać. Każdy chce być geniuszem, bo oni nie popełniają błędów. Najprostsza droga - "jeżeli nikt nie dostrzeże moich błędów, stanę się nim".
  • Nie ważna jak mądry jesteś, jeżeli nie umiesz współpracować z ludźmi, nie odniesiesz sukcesu.
  • Lose the Ego. Każdy chciałby być dumny z projektu, który tworzy.
  • Krytykowania i odbierania krytyki trzeba się nauczyć.
  • Strach przed porażką jest czymś normalnym. Za każdym razem, gdy odnosisz porażkę uczysz się czegoś. Nie ma lepszej metody. Porażki są dobre dopóki ich nie powtarzasz.
  • Praktyka jest kluczem! Not just fail, fail quickly.
  • Be a small fish - słuchaj rad, pokaż że jesteś otwarty na zmiany. Jesteś silny bo nie boisz się popełniać błędów.
  • Duże zmiany np. architektoniczne powinny być dostrzegane jak najwcześniej, z czasem będzie coraz trudniej coś poprawić. Z kolei, gdy ktoś przychodzi z pomysłami za wcześnie, powstaje jałowa dyskusja, z której trudno wytworzyć nawet prototyp. Trzeba wyczucia. Współpracuje wcześnie i często.
  • Zwróć uwagę na narzędzie - jak one wpływają Twoją współpracę z innymi. Zwróć uwagę na terminy.
  • Dokumentuj swoje porażki.

I jeszcze jeszcze nowy miernik, o którym się dowiedziałem :)
Bus factor - ile osób w zespole musi zostać rozjechanych przez autobus, żeby projekt trafił szlag.

2 maja 2013

constexpr (klasa będąca literałem)

Jeżeli chcemy stworzyć obiekt constexpr, konstruktor klasy także będzie musiał być constexpr. Klasa będzie wtedy literałem. Taki konstruktor będzie zazwyczaj pusty i będzie musiał zainicjować wszystkie zmienne na liście inicjalizacyjnej.

constexpr nie gwarantuje, że kod zostanie wykonany podczas kompilacji. Można się jednak co do tego upewnić stosując static_assert, które to niejako wymusza. Ciekawostka, w C++20 zostanie wprowadzone nowe słowo kluczowe consteval, które gwarantuje że funkcja zostanie wygenerowana podczas kompilacji (przykład na samym końcu).
#include <iostream>

using namespace std;

class MyClass {
public:
    constexpr MyClass(int arg) : a{arg}, b{0} {
        b = 4;
    }

    int a;
    int b;
};

int main()
{
    constexpr MyClass cobj{3};
    static_assert(cobj.a == 3 and cobj.b == 4, "Doesn't match at compile time");

    cout << cobj.a << " " << cobj.b << endl;
}
Wynik:
3 4
constexpr dodaje implicit const do deklarowanych obiektów (w C++11 dodawał on również const do funkcji, jednak w C++14 z tego zrezygnowano). Jeżeli więc chcielibyśmy rozbudować klasę o metodę, która pozwala na modyfikowanie pól, kompilacja zakończy się niepowodzeniem.
#include <iostream>

using namespace std;


class MyClass {
public:
   constexpr MyClass(int i) : val(i) {}
   constexpr void set(int num) {
       val = num;
   }
   constexpr int get() const {
       return val;
   }

   int val;
};

int main()
{
    constexpr MyClass cobj{4};

//  cobj.set(44);               // Error - shake powinno być const
    constexpr int v = cobj.get();
    static_assert(v == 4, "Doesn't match at compile time");
    cout << v << endl;

    return 0;
}
Wynik:
4
Jedyny znany mi sposób na ominięcie tego problemu, to stworzenie nowej funkcji (także constexpr). Deklarowane w niej zmienne domyślnie będę constexpr, więc nie trzeba explicit podawać tego specyfikatora, nie zostanie dodany const, a więc będzie można zawołać metodę set() bez żadnego problemu.
#include <iostream>

using namespace std;


class MyClass {
public:
   constexpr MyClass(int i) : val(i) {}
   constexpr void set(int num) {
       val = num;
   }
   constexpr int get() const {
       return val;
   }

   int val;
};

constexpr MyClass change() {
   MyClass cst{4};
   cst.set(56);
   return cst;
}

int main()
{
   constexpr MyClass cst = change();
   constexpr int v = cst.get();
   static_assert(v == 56, "Doesn't match at compile time");
   cout << v << endl;

   return 0;
}
Wynik
56
Wymuszenie generowania funkcji podczas procesu kompilacji (C++20), za pomocą consteval.
#include <iostream>

using namespace std;


class MyClass {
public:
   constexpr MyClass(int i) : val(i) {}
   constexpr void set(int num) {
       val = num;
   }
   constexpr int get() const {
       return val;
   }

   int val;
};

consteval MyClass change() {
   MyClass cst{4};
   cst.set(56);
   return cst;
}

int main()
{
   constexpr MyClass cst = change();
   constexpr int v = cst.get();
   static_assert(v == 56, "Doesn't match at compile time");
   cout << v << endl;

   return 0;
}

W tej chwili działa tylko z clang-iem (wersja 9.0.0-2):
$ clang++ -std=c++2a main.cpp
$ ./a.out
56

1 maja 2013

Delegating constructors

Delegating constructors używają innych konstruktorów z klasy do wykonania inicjalizacji, dzięki temu nie trzeba już korzystać z metod pomocniczych, by uwspólnić powtarzające się części kodu.
#include <iostream>

class Value {
public:
    Value(int i) {
        std::cout << "Value " << i << std::endl;
    }
};

class MyClass {
public:
    MyClass() : MyClass(4) {
        std::cout << "Constructor 1" << std::endl;
    }
    MyClass(int v) : MyClass("MyClass", v, 5) {
        std::cout << "Constructor 2" << std::endl;
        value3 = Value(77);
    }
    MyClass(std::string s, int v1, int v2) : value1(v1), value2(v2), value3(66) {
        std::cout << "Constructor 3" << std::endl;
        value3 = Value(88);
    }
private:
    Value value1;
    Value value2;
    Value value3;
};

int main()
{
    MyClass m;
    return 0;
}
Wyniki:
Value 4
Value 5
Value 66
Constructor 3
Value 88
Constructor 2
Value 77
Constructor 1
Wygląda na to, że na liście konstruktora, który oddelegowuje swoje działanie może się pojawić tylko i wyłącznie inny konstruktor (żadne zmienne) - wywnioskowane na podstawie informacja z clang-a.
$ clang++ main.cpp -std=c++11
main.cpp:12:17: error: an initializer for a delegating constructor must appear alone
    MyClass() : value3(11), MyClass("MyClass", 4, 5) {
                ^~~~~~~~~~
1 error generated.