Co nowego w C++11? Część 1

Nie tak dawno we wrześniu został oficjalnie wydany nowy standard języka C++: C++11. Po 8 latach od poprzedniej edycji tego języka wprowadzono dosyć dużo nowinek zarówno w standardowej bibliotece STL jak i w samej składni języka. Postaram się pokrótce przedstawić (mam nadzieje, iż w kilku częściach) nowinki, które najbardziej mnie przypadły do gustu i które są wspierane przez co najmniej jeden z dwóch kompilatorów: GCC i/lub MS VC++. Napisałem “co najmniej”, gdyż na chwilę obecna niestety nie wszystkie kompilatory wspierają bądź co bądź jeszcze świeży standard. Aczkolwiek wiele rzeczy już jest zaimplementowane.

Przechodząc już do konkretów.. Pierwszym elementem, który chciałbym opisać jest nowe słowo kluczowe auto. Może nie jest ono do końca nowe, gdyż istniało już w poprzednich wersjach standardu, jednakże kompletnie zmieniło ono znaczenie. Zadaniem słowa kluczowego auto jest ułatwienie i skrócenie czasu pisania programów. Chodzi tutaj o automatyczne (ale co najważniejsze statyczne) wnioskowanie typu zmiennej na podstawie wyrażenia r-value na etapie kompilacji kodu.

Na przykład:

    auto i = 5;  		// zmienna typu int
    auto j = 2.5;		// zmienna typu float
    std::vector<double> v;
    auto it = v.begin(); 	// zmienna typu std::vector<double>::iterator

Jak widać w pierwszym i drugim przykładzie kodu co prawda niewiele zostało zaoszczędzone. Ale załóżmy, że w naszym projekcie powszechnie stosujemy iteratorów oraz za każdym razem chcemy deklarować nowe zmienne przechowujące odpowiedni iterator. W takim przypadku należy zazwyczaj podać pełna nazwę typu, np.: std::vector<double>::iterator. Pól biedy gdy mamy do czynienia z szablonami o jednym argumencie, gorzej natomiast, gdy chcemy iterować po nieuporządkowanej mapie której kluczem jest para dwóch liczb całkowitych a wartościami tablice liczb zmiennoprzecinkowych. No ok, może przypadek trochę wyolbrzymiony, ale porównajmy zapisy:

    std::unordered_map<std::pair<int, int>, std::vector<double> > collection;
    std::unordered_map<std::pair<int, int>, std::vector<double> >::iterator it_manual = collection.begin();
    auto it_auto = collection.begin();

oraz pętle:

    for (std::unordered_map<std::pair<int, int>, std::vector<double> >::iterator it_manual = collection.begin(); it != collection.end; ++it) {
        ....
    }
    for (auto it_auto = collection.begin(); it != collection.end; ++it) {
        ....
    }

Jak można łatwo zauważyć otrzymujemy zarówno duży zysk płynący zarówno z oszczędności pisania kodu jak i jego czytelności, no i najważniejsze, że nie potrzeba już pamiętać jakiego dokładnie typu była nasza kolekcja.

Obliczenia na typach generycznych w C#

Rozwijam właśnie aplikację w której niezbędne jest przeprowadzanie obliczeń na niekoniecznie z góry znanych typach. W celu zapewnienia elastyczności poszczególnych klas zdecydowałem się na implementacje klas generycznych. Załóżmy, że mamy taką oto klasę:

1
2
3
4
public class Klasa< T > {
    public T Field1;
    public T Field2;
}

Następnie utwórzmy metodę, która ma za zadanie zsumować oba pola i zwrócić wynik. Intuicyjnie powstanie taki kod:

1
2
3
    public T SumFields() {
        return Field1 + Field2;
    }

Jednakże podczas kompilacji zostaniemy uraczeni następującym błędem:
Error 1 Operator '+' cannot be applied to operands of type 'T' and 'T'

Po chwili zastanowienia nie można się kompilatorowi dziwić, nie każdy typ musi mieć zaimplementowany operator dodawania (a także odejmowania, mnożenia, itd.). Znająć z kolei chęci programistów Microsoft do projektowania interfejsów do „wszystkiego” zaczałem szukać takiego, który deklarowałby powyższe operatory. Niestety czegoś takiego jeszcze nie ma (może kiedyś będzie?). I należy się posiłkować innymi metodami.

W tym celu można wykorzystać wprowadzony w C# 4.0 nowy typ dynamic. Deklarując zmienną typu dynamic zmieniamy moment, w którym rozwiązywany jest jej typ. Normalnie proces ten jest realizowany w czasie kompilacji, natomiast w przypadku dynamic w czasie działania programu (podobnie jak w językach skryptowych bez ścisłego typowania).

Po drobnej modyfikacji metoda będzie wyglądała następująco:

1
2
3
4
    public T SumFields() {
        dynamic dField1 = Field1, dField2 = Field2;
        return dField1 + dField2;
    }

Windows SideShow i Windows Mobile

Windows SideShow jest usługą, która umożliwia szybki dostęp do niektórych funkcji systemu (zależnych tylko od wyobraźni programistów) z między innymi urządzeń posiadających mały ekran W sumie nie widziałem jeszcze działania tego podsystemu, aż do wczoraj…

W sumie zainteresowałem się tym już jakiś tydzień temu szukając jakichś fajnych gadżetów na mojego HTC Touch Diamonda (dorobiłem się w końcu jakiś czas temu, tak jak i nowego laptopa;) ). No i przypomniałem sobie o Windows Sideshow – usłudze, której nazwa co chwile wpadała w oczy podczas przeglądania panelu sterowania. Grzebiąc trochę w Internecie doszukałem się, że Microsoft wypuścił aplikacyjkę na Windows Mobile wykorzystującą właśnie Windows SideShow. Jaki jest efekt działania?

W tej chwili na komórce w momencie gdy jest połączona przez Bluetooth (po WiFi nie działa) mogę połączyć się z usługą Windows SideShow na laptopie i korzystać z zainstalowanych gadżetów. Gadżety można ściągnąć z MS Live Gallery, ale na chwilę obecną nie ma ich niestety zbyt wiele. W każdym bądź razie są dwa dosyć przydatne.

Office Powerpoint Remote oraz domyślnie zainstalowany gadget do Windows Media Playera. Ten drugi umożliwa zdalne sterowanie naszym WMP, a ten pierwszy czyni z naszego telefonu dosyć zaawansowany prezenter, który oprócz możliwości przełączania slajdów wyświetla również napisane notatki oraz wyświetla jaki będzie następny slajd. Co prawda ma jeszcze parę małych wad jak kiepskie przystosowanie do dotykowego ekranu, ale i tak cieżko znaleźć podobną aplikację i to za darmo :)

Linki:


Zarządzanie pamięcią pod Windowsem x64

Pracując przy projekcie, który bardzo mocno wykorzystuje obliczenia matematyczne, po raz pierwszy natknąłem się w praktyce na ograniczenia pamięciowe. W pierwszych wersjach systemu nie udało nam się tych ograniczeń zauważyć, gdyż operowaliśmy na małych próbkach danych rzędu 1000×20. Oczywiście należało sprawdzić działanie także dla większej próbki danych i wyskoczyło nam pierwsze z ograniczeń.

Początkowo prace deweloperskie przeprowadzane były na maszynie z systemem 32bitowym z pamięcią 4GB. Występuje tutaj zjawisko ograniczenia pamięci dla procesu do 2GB (ewentualnie do 3GB z odpowiednim przełącznikiem). Przez jakiś czas wystarczał nam obecny limit… Ale po co się ograniczać skoro procesory są 64bitowe? Tak więc po przeinstalowaniu systemu na Windows Server 2008 x64 nastąpiła przedwczesna radość z praktycznie zerowymi limitami (nie mam do dyspozycji serwera z 2TB pamięci RAM ;) ).

Pierwszym problem przy uruchamianiu aplikacji pojawił się z bibliotekami matematycznymi Intel Math Kernel Library, były jeszcze 32bitowe, ale nie sprawiło dużo kłopotów przerobienie jej na wersję 64bitową (aczkolwiek trzeba było trochę doczytać;P ).

W końcu z wielką radością w oczach aplikacja została odpalona i można było patrzeć jak rośnie słupek zajętości pamięci przez proces, urósł do około 4GB. No dobra, ale jakie tutaj są ograniczenia? Okazuje się, że najbliższym limitem jest ciągł obszar jaki można zaalokować. Próba zdefiniowania tablicy typu Byte o liczbie elementów większej niż 2^31 zakończyła się porażką (mniejszą bądź równą – należy wziąć jeszcze narzut platformy .NET – jak najbardziej udało się zaalokować). Dlaczego akurat tablica może mieć rozmiar tylko 2GB? No cóż, wina leży po stronie platformy .NET i środowiska CLR.

Następny krokiem była próba zaalokowania kilku tablic po 2GB każda. Niestety po 6 takich wyświetlił mi się wyjątek OutOfMemoryException (patrz rysunek)

Przyczyną braku pamięci zapewne była wielkość pliku wymiany, gdyż odpowiedni licznik pokazywał ładną liczbę 15999MB (rysunek). Zajętość co prawda wyniosła 14484MB, ale kolejne 2GB tutaj już się nie mieszczą ;)

Ostatecznie muszę stwierdzić, że o ile limit 2GB na wielkość ciągłej pamięci do zaalokowania może boleć (należałoby się zagłębić bardziej w jaki sposób można to obejść), to obszar pamięci dla jednego procesu jest jak na razie nie do zapełnienia dla mnie pożytecznymi danymi, a zawsze można jeszcze zwiększyć rozmiar pliku wymiany.

Warto przeczytać:

  1. BigArray<T>, getting around the 2GB array size limit
  2. Memory Limits for Windows Releases
  3. Zarządzanie pamięcią w 32 i 64-bitowych systemach Windows

Ns2 i Ns3

Ostatnio przypomniały mi się moje laboratoria z sieci komputerowych i telefonii IP. Polegały głównie na łączeniu komputerów, konfigurowania routingów, translacji adresów, podsłuchiwanie sieci. Czasem dostaliśmy również jakiś ciekawszy sprzęt do zabawy (w stylu router Cisco), ale teraz muszę przyznać, że to były straszne nudy.

Obecnie zostałem poniekąd „zmuszony” do zapoznania się z symulatorami sieci i ocenienia, które z nich spełnią odpowiednie wymagania. Kilka takowych się znalazło (m.in. opnet, omnet++),  ale ich wadami są jak zwykle albo trudnodostępność, albo fakt, że nie są już rozwijane od dłuższego czasu. W momencie, gdy dotarłem do ns2 (w sumie to od niego zacząłem tę przygodę:) ) muszę przyznać, że ktoś odwalił kawał dobrej roboty.

ns2 jest to projekt open-source stworzony przez badaczy dla badaczy. Rdzeń symulatora jest napisany w pełni obiektowo w C++, a skrypty eksperymentów (i nie tylko) można pisać w obiektowej wersji Tcl – OTcl. ns2 umożliwa symulowanie praktycznie każdej warstwy modelu OSI/ISO, począwszy od warstwy fizycznej, poprzez sieciową (wraz z włączeniem różnych protokołów routingu), aż po zachowanie się protokołów warstwy transportowej i aplikacji.

W celu przetestowania własnego protokołu (którejkolwiek z warstw) „wystarczy” go tylko zaimplementować w symulatorze. Ostatecznie nie jest to dosyć trudne, gdyż można wzorować się na już napisanych algorytmów.

Efektem działania symulatora jest plik tekstowy w którym zawarte są wszystkie niezbędne informacje na temat każdego z pakietów jaki w naszej wirtualnej sieci podróżował. Wystarczy teraz zaprząc jakiś parser (np. awk), coś do rysowania wykresów (np. gnuplot) i już możemy się cieszyć naszymi wynikami :)

Dodatkowo jeżeli chcielibyśmy wizualnie zobaczyć co się w naszej sieci dzieje można skorzystać z programu NAM (Network Animator) towarzyszącego ns2. Który na podstawie odpowiedniego pliku wynikowego z ns2 stworzy ładną animacje ruchu na poszczególnych połączeniach węzłów.

To tyle jeżeli chodzi o ns2. Chciałbym jeszcze wspomnieć o kolejnej wersji symulatora ns3, która jest w trakcie powstawiania. W przeciwieństwie do ns2, ns3 jest w całości napisana w C++ (ns2 korzysta również z OTcl) i jak na razie skrypty eksperymentów to są programy napisane w C++ (aktualnie powstają bindingi do pythona). Ciekawostką jeżeli chodzi o ns3 jest to, że wspiera on format plików pcap, dzięki czemu możemy bezproblemowo odczytać wyniki naszej symulacji albo poprzez tcpdump, albo wireshark <– czyż to nie jest dużo wygodniejsze niż pliki tekstowe? :P

Więcej informacji można znaleźć na stronie symulatora:

  1. ns2 i nam
  2. ns3

Matematyka i C#

Wiele razy już w przeciągu ostatnich 2-3 lat szukałem jakiejś porządnej biblioteki implementującej podstawowe algorytmy z algebry liniowej i ogólnie z matematyki. Wbrew pozorom znalezienie tychże dla platformy .NET nie jest takie trywialne..

Po odpytaniu google o jakikolwiek silnik matematyczny natrafiamy na cenioną open sourcową bibliotekę GSL. Z mojego punktu widzenia ma ona jedną wadę: jest mocno nastawianona na środowisko Linuksowe. O ile istnieją wersje przeportowane na system Win32 (nie mówię tutaj o Cygwinie) to nie były już aktualizowane kilka lat. A o automagicznym podpięciu się z poziomu C# można praktyczznie pomarzyć..

Następna kolekcja matematyczna jak wpadła mi w ręce to dnAnalytics. Muszę przyznać, że jest ona bardzo dobrze przygotowana zapewniając bardzo wygodny i intyuicyjny intefejs programistyczny. Z podstawowych możliwości udostępnianych mogę wymienić własną implementację struktury do obsługi macierzy i wektorów, możliwość wczytywania i zapisywania danych z/do plików CSV, chyba wszystkie najważniejsze operacje na macierzach oraz szereg algorytmów do rozwiązywania równań, dekompozycji macierzy (SVD, LU, QR) i co najważniejsze ma zaimplementowane wsparcie dla natywnej biblioteki Intel Math Kernel Library, dzięki czemu znacząco zyskujemy na wydajności. Niestety dnAnalytics miał jedną wadę: nie w pełni wykorzystywał możliwości MKL.

W ten sposób dotarłem (całkiem przypadkowo) od ILNumerics.Net. Porównując z dnAnalytics to ma raczej trochę inny interfejs (ani nie lepszy, ani nie gorszy, po prostu inny) przypominający bardziej język skryptowy  z Scilaba czy Matlaba (między innymi odwoływanie się do pól macierzy). A skoro jestem przy macierzach to warto wspomnieć, że w tym przypadku nie jesteśmy ograniczeni tylko do dwóch wymiarów i typu double, ale dzięki typom generycznym możemy wykorzystać niemal dowolny typ numeryczny w wielomiarowej przestrzeni. Malutkim minusem jest brak obsługi plików CSV do których przyzwyczaiła mnie dnAnalytics, ale coś za coś. Na zakończenie jeszcze jeden ogromny plus: w pełni wykorzystuje zestaw funkcji LINPACK z natywnej biblioteki MKL, a także dodatkowo z konkurencyjnej biblioteki firmy AMD. Dla zachowania kompatybilności z platformą mono powstała również specjalna wersja natywnego silnika matematycznego.

Podsumowując dnAnalytics jest bardzo przejrzystą biblioteką nadającą się do projektów w których nie wymaga się dużej wydajności i ograniczamy się do dwóch wymiarów. ILNumerics.Net jest natomiast prawdziwą perełką wśród otwartych bibliotek matematycznych, a dzięki porządnemu połączeniu z MKL i pochodnymi jest naprawdę wydajnym tworem.

Visual Studio 2008 SP1 Beta i .NET 3.5 SP1 Beta

Parę dni temu coś mnie podkusiło do instalacji wersji beta nowego service packa do VS2008 i .NET3.5. Instalacja przeszła względnie bezboleśnie (musiałem tylko zwolnić trochę miejsca na dysku;) ).

I wszystko byłoby OK, gdyby nie fakt, że RubiksCube v2 przestał działać..
A dokładniej po wykonaniu dowolnego ruchu wywalał się bliżej nieokreślony błąd w PresentationCore.dll (bliżej nieokreślony, bo opisu błędu wogóle nie było). Co lepsze przed SP1 Beta aplikacja działała prawidłowo…

Błędu szukałem komentując po kolei poszczególne fragmenty kodu i dotarłem do takiej konstrukcji

if (model == null)
{
    model = new Model3DGroup();
}
else
{
    model.Children.Clear();
}

Gdzie model jest typu Model3DGroup. Doszedłem do tego, że aplikacja wywalała się przez linijkę 7. Nie wiem dlaczego tak się zachowywał, ale błąd naprawiłem w następujący sposób:

if (model == null)
{
    model = new Model3DGroup();
}
else
{
    model.Children = new Model3DCollection();
}

I jak na razie aplikacja śmiga :P

Kostka, kosteczka i kostkunia

Dokonałem ostatnio dosyć spontanicznego zakupu dwóch kostek Rubika. Nie byłoby w tym nic dziwnego gdyby nie to, że nie są to już klasyczne kostki 3x3x3, ale 4x4x4 oraz 5x5x5 :) . Nie był to może mały wydatek, ale jestem bardzo z niego zadowolony. Wraz z swoją starszą siostrą można je podziwiać na poniższych zdjęciach :P

050208-1541-kostkakoste1.jpg
050208-1541-kostkakoste2.jpg

Jeżeli chodzi o zdolność układania to 4x4x4 udało mi się po raz pierwszy ułożyć po około 2h. Następne próby trwały już około 1h. W następnej kolejności zabrałem się za piątkę, ostatecznie również skończyłem po około godzinie :) . Od teraz chyba nie będę się nudził już na żadnym z wykładów :D

Debugger w Visual Studio

Jak wiadomo debugger dla programisty to jest bardzo ważne narzędzie, które potrafi znacząco ułatwić pracę. Moim zdaniem w Visual Studio znajduje się chyba jeden z najlepszych pod względem intuicyjnego używania (aczkolwiek dawno nie korzystałem z produktów Borlanda, tam może też się co nieco zmieniło;) ). Nie będę tutaj opisywał jak z poziomu środowiska IDE obsługiwać to narzędzie, gdyż to raczej każdy już sie z tym zapoznał, ale jak z niego korzystać z poziomu kodu.

W ogólności służy do tego klasaSystem.Diagnostics.Debugger. Co prawda posiada ona tylko trzy metody przydatne dla programisty, ale liczba ich nie jest istotna;).

  • Debugger.Break() – powoduje zatrzymanie działania programu i włączenie debuggera (coś a la standardowy breakpoint)
  • Debugger.Launch() – uruchamia debugger i podłącza go do aktualnego procesu, ale jeszcze go nie zatrzymuje
  • Debugger.Log(int level, string category, string message – wykorzystuje w przypadku, gdy chcemy wysłać na konsolę debuggera informację zwrotną. Nie potrzebujemy w tym wypadku tworzyć aplikacji konsolowej – w ogóle nie musi ona posiadać jakiegokolwiek okna ;)

Ciekawostką jest to, że wywołania tych metod zostają usuwane w wersji Release projektu. Są one dostępne jedynie w wersji Debug.
Przydatności metody Log() chyba nie trzeba tłumaczyć. Zawsze przecież przydają się dodatkowe informacje w przypadku, gdy badamy rozbudowaną i przebiegającą przez wiele iteracji funkcję.

Break() możemy oczywiście zastąpić standardowym Breakpointem z IDE, ale ma taką zaletę, że zadziała nawet wtedy gdy nie będziemy uruchamiali aplikacji z VisualStudio. Przydatne przy badaniu WebService’ów zainstalowanych na IIS;)

Sąsiednią klasą towarzyszącą System.Diagnostics.Debugger jest System.Diagnostics.Debug udostęniająca asercje (Assert(bool condition) – przy czym warunek musi być spełniony, aby program działał poprawnie) oraz bardziej rozbudowane metody do obsługi wyjścia w konsoli debuggera takie jak Write, WriteIf, WriteLine, WriteLineIf, Indent, Unindent, Print. W każdym bądź razie polecam się z nimi zapoznać :)

Wykorzystanie wyrażeń lambda w naszych klasach

Wyrażenia lambda jest to nowa konstrukcja wprowadzona do C# 3.0 pozwalająca w łatwy i przejrzysty sposób na „szybko” zdefiniować jakąś funkcję. W tej notce napiszę jak można je użyć do definiowan

Nadrzędnym typem każdego wyrażenia lambda jest typ Func<T1, T2, T3, T4, TResult> gdzie T1-T4 są typami argumentów, a TResult typem wyniku. Nie jesteśmy oczywiście przywiązani do czterech parametrów wejściowych, gdyż zostały zdefiniowane typy od 0 do 4 argumentowe (zmienia się liczba wystąpień TX). TResult jest natomiast parametrem obowiązkowym.

W poniższym przykładzie zdefiniuję metodę w klasie Matrix, która jako argument będzie otrzymywała wyrażenie lambda (funkcję) i wykona ją dla każdego z elementów macierzy. Przechodząc do praktyki będzie to wyglądało tak:

        public void DoForAllElements(Func< T , T> function)
        {

            for (int i = 0; i < N; i++)
            {
                for (int j = 0; j < M; j++)
                {
                    this[i, j] = function(this[i, j]);
                }
            }
        }

Wykorzystać ją możemy w sposób następujący:

var A = new Matrix< Double , DoubleCalculator>();
A.DoForAllElements( x => Math.Sqrt(x) );

Prawda, że proste? :)

Previous Entries