Mechanizm promocji w C++W standardzie języka C (i C++) opisany jest mechanizm promocji, który polega na automatycznej konwersji z mniejszego typu na większy (np. short na int). Istnieją dwa rodzaje promocji:
Zachowanie to jest bardzo intuicyjne. Dodatkowo, w przypadku użycia funkcji o zmiennej ilości argumentów (tzn. użycia ...), małe typy całkowitoliczbowe ulegają promocji do int / unsigned int, a float do double. To jest już trochę mniej naturalne, ale ta wiedza może czasami się przydać (choćby do tego, żeby tego nie używać ;)).
W C++, z mechanizmem promocji wiąże się dodatkowy problem przy pisaniu funkcji szablonowych. Typowa implementacja max wygląda tak:
template <typename T>
const T& max(const T& a, const T& b)
{
return (a > b) ? a : b;
}
Wszystko byłoby fajnie, gdyby nie fakt, że wywołanie max(1.0f, 2.0) się nie skompiluje. Da się to rozwiązać bawiąc się trochę szablonami -- o tym w dalszej części.
Szablony w C++Szablony są bardzo ciekawym elementem języka C++. Zdarza mi się przeglądać nie mój kod i często mam wrażenie, że ich użycie (poza wykorzystywaniem biblioteki STL) ogranicza się do tworzenia uniwersalnych funkcji i struktur danych, a to dopiero wierzchołek góry lodowej.
Co właściwie można osiągnąć za pomocą szablonów?
Ostatnie dwa punkty mogą wymagać dodatkowego wyjaśnienia.
Jeżeli chodzi o generowanie zoptymalizowanego kodu źródłowego, to za pomocą szablonów możemy generować kod, który nie ustępuje pod względem wydajności ręcznej implementacji. Możliwa jest implementacja klasy wektora (matematycznego), gdzie rozmiar jest parametrem szablonu, a wydajność jest zawsze taka jak w przypadku optymalnej implementacji dla każdego rozmiaru z osobna.
W kwestii budowania uniwersalnych wzorców zachowań sprawa jest trochę bardziej skomplikowana -- mam tu na myśli programowanie z użyciem wytycznych, które opisuje Andrei Alexandrescu w książce "Modern C++ Design". Podczas implementacji często istnieje wiele możliwych (i sprzecznych ze sobą) rozwiązań. Czy kod ma być thread-safe? Jak ma wyglądać alokacja pamięci? Decyzje o tym powinien podejmować użytkownik kodu w zależności od swoich potrzeb i takie właśnie zachowanie można osiągnąć używając szablonów w C++.
Mechanizm szablonów ma bardzo dużą siłę ekspresji, rzadko spotykaną w innych językach. Aby nie pozostać gołosłownym, w najbliższym czasie pojawi się seria wpisów z użyciem szablonów.
Continuous integrationJedną z rzeczy na które kładziony jest nacisk w zwinnych metodologiach (ang. agile) to ciągła integracja. Pod tą nazwą nie kryje się nic innego jak częste, automatyczne czynności dotyczące projektu, zwykle kompilacja i uruchamianie testów jednostkowych i/lub regresyjnych. Ma to na celu szybkie wykrywanie problemów i ich rozwiązywanie. Ponieważ problemy są wykrywane szybko, powinno dać się je dość łatwo wyeliminować. Dzięki temu w każdym momencie mamy dostęp do sprawnego builda.
Przykładowe zalety takiego podejścia:
Podczas każdego builda powstają pliki, które możemy chcieć zachować -- nazywane są artefaktami. Mogą to być logi, wyniki testów, pliki wykonywalne, informacje o użytych plikach itp. Pozwala to na jeszcze lepsze śledzenie zmian w projekcie i znajdowanie problemów.
Najważniejszą rzeczą w całej idei continuous integration jest automatyzacja. Możemy kupić potężny komputer za kilka tysięcy, który będzie to wszystko robił i można znacząco zmniejszyć obciążenie członków zespołu. To inwestycja, która na pewno zwróci się bardzo szybko.
Tyle w teorii -- a jak to wygląda w praktyce? Na licencji MIT dostępny jest Hudson. Jego konfiguracja jest bardzo prosta i współpracuje chyba z każdym popularnym systemem kontrolii wersji ;) Sam używam go na komputerze Mac Mini do kompilacji 3 projektów -- frameworka i aplikacji, które go używają. Jak wiadomo, aplikacje pod iPhoneOS można niestety kompilować tylko pod systemem Mac OS X. Dzięki takiemu rozwiązaniu w kilkadziesiąt sekund od aktualizacji repozytorium mam przygotowanego zipa ze skompilowaną aplikacją, którą mogę pobrać w każdym miejscu na świecie przez przeglądarkę i zainstalować przez iTunes pod Windows. Ma to ogromne znaczenie w zespołach, które mają dostęp tylko do jednego komputera Mac.
Dlaczego nie warto używać Dev-C++Często wracającym tematem na warsztacie są mniejsze lub większe problemy z działaniem / używaniem Dev-C++. Rozpoczyna się wtedy święta wojna zwolenników i przeciwników tego narzędzia. Jest to sytuacja o tyle dziwna, że o ile typowy flame w stylu Windows vs Linux lub DirectX vs OpenGL ma jeszcze jakiś sens i można podać sensowne argumenty dla każdej ze stron, o tyle bronienie Dev-C++ jest poważnym błędem i powinno być karalne.
Tak, Dev-C++ jest zły. Nie mówię, że taki był zawsze -- sam kiedyś z niego korzystałem, tak samo jak wiele innych osób, które są teraz jego przeciwnikami. Co sprawia, że to narzędzie wywołuje aż takie skrajne emocje?
Problem jest o tyle duży, że dużo początkujących programistów trafia na Dev-C++ jako polecane środowisko w tutorialu bądź książce. Ostatnio spotkałem się też z sytuacją, gdzie w jednym z Wrocławskich liceów używają tego archaicznego narzędzia.
Jeżeli dla kogoś jedynym powodem zostania przy Dev-C++ jest to, że go już poznał, to czas, żeby poznać coś nowego. To będzie musiało i tak prędzej czy później nastąpić, skoro nie jest od lat rozwijany. Zawsze można spróbować Visual C++ Express, Eclipse, Code::Blocks (na stronie jest stara wersja, aktualną można znaleźć tutaj) albo NetBeans.
Dwa nowe projektyPowoli pracuję teraz nad dwoma projektami, o których jeszcze nie było wspominane. Czas najwyższy to zmienić.
GraphIt (zalecana wymowa 'po naszemu' to po prostu grafit ;)) jest biblioteką dla .NET wspierającą rysowanie różnego rodzaju grafów i diagramów. Będzie stanowiła część mojej pracy magisterskiej i docelowo będzie udostępniona na licencji LGPL. Z powodu kilku problemów technicznych powstaje wolniej niż się spodziewałem, ale powinno się to już zmienić. Mam nadzieję, że w przyszły weekend będę mógł pokazać screeny z jakiegoś prostego użycia.
Jestem w trakcie okrajania Wombata, żeby stał się trochę lżejszy i łatwiejszy w użyciu. Trochę rzeczy w oryginalnym projekcie było zrobionych nie do końca dobrze, brakowało też m.in. obsługi dźwięku, którą trzeba w końcu dodać. Przy okazji pracuję nad większym odseparowaniem rzeczy związanych z konkretnym systemem operacyjnym i renderer jest przenoszony na OpenGL. Biblioteka wciąż będzie zamknięta, ale możliwe, że niedługo pojawią się jakieś małe gierki oparte na tej bibliotece (ale za wcześnie jeszcze na szczegóły).
Radix sortDawno, dawno temu na slashdot pojawił się wpis Sort Linked Lists 10X Faster Than MergeSort. Okazało się, że twórca algorytmu wykazał się brakiem wiedzy i zaproponował algorytm Radix Sort.
Dzisiaj sam zaimplementowałem ten algorytm (okazało się to prostsze niż się spodziewałem) chcąc sprawdzić jak wypada w porównaniu do qsort z cstdlib i sort z STL.
Zrobiłem testy losując sporą ilość intów, a następnie sortując je każdym z algorytmów. Na koniec sprawdzam czy wynik działania wszystkich algorytmów jest taki sam. Wyniki okazały się zaskakujące -- nie spodziewałem się aż tak dużych różnic czasu. Wyniki poniżej.
Compo 3hW poprzedni weekend na warsztacie zostało zorganizowane trzygodzinne compo.
Jeszcze nigdy nie brałem udziału w tak krótkim pisaniu gry na czas, ale postanowiłem, że zobaczę co z tego wyjdzie. Ostatecznie zająłem 2 miejsce i jestem z tego zadowolony.
Gra (oczywiście niedokończona ;)), którą pisałem była klonem crimson landa i wypadła nienajgorzej. Całkiem ciekawe wydają mi się szczegóły techniczne, bo jadąc do domu na święta nie miałem dostępu do swojego kodu i pisałem kompletnie od zera:
Ciekawostką jest też to, że na procesorze C2D 1.8 GHz, użycie procesora według Menadżera zadań Windows nie przekracza 2%. Jak widać proste gry można spokojnie robić bez jakiejkowiek akceleracji ;)
Grę można zobaczyć tutaj.
Wynalazek zwany PIMPLPisząc biblioteki w C++ często można natrafić na problem wytworzenia wystarczająco dobrej enkapsulacji. Definicje klas, znajdujące się w plikach nagłówkowych, często pokazują za wiele: wszystkie prywatne pola i metody, które moglibyśmy chcieć ukryć. Dodatkowym problemem jest to, że praktycznie każda zmiana w implementacji wymaga zmiany definicji klasy, a to często przekłada się na konieczność ponownej kompilacji całego projektu.
Okazuje się, że istnieje bardzo proste rozwiązanie tego problemu -- PIMPL (private implementation):
// plik object.h
class ObjectImpl;
class Object
{
ObjectImpl* impl;
public:
Object();
~Object();
int SampleMethod(int x);
};
// plik object.cpp
class ObjectImpl
{
// Właściwa implementacja
};
Object::Object()
{
impl = new ObjectImpl();
}
Object::~Object()
{
delete impl;
}
int Object::SampleMethod(int x)
{
return impl->SampleMethod(x);
}
Oczywiście wypdałoby jeszcze dopisać konstruktor kopiujący i operator przypisania jeśli to konieczne.
Dodatkowo, można zmienić całkowicie implementację, a dla użytkowników klasy wszystko będzie wyglądać dokładnie tak samo. Jako lekturę uzupełniającą polecam to i stary wątek na forum gamedev.pl.
SVNLudzie mają wiele powodów, dla których nie używają systemów kontroli wersji. Najpopularniejsze z nich to chyba 'bo pracuję sam' i 'nie potrzeba mi tego' -- nie mam zamiaru tłumaczyć dlaczego te osoby są w błędzie ;) Ja osobiście miałem trochę inny powód -- nie chciało mi się z tym bawić.
Jestem jedną z tych osób, które nie do końca ufają serwisom zapewniającym hosting repozytoriów svn, ponieważ nie są one w żaden sposób szyfrowane. Dlatego jedynym rozwiązaniem, które mnie satysfakcjonuje, jest trzymanie repozytorium na prywatnym komputerze.
Artykuły