background image

11/2009

48

Perełki C++

Statyczne asercje w C++

www.sdjournal.org

49

M

echanizm statycznych asercji zna-
ny  jest  już  od  wielu  lat,  aczkol-
wiek  –  jak  wynika  z  doświad-

czeń  autora  niniejszego  tekstu,  stosunkowo 
niewielu  programistów  stosuje  w  praktyce 
ten  użyteczny  idiom.  Wydaje  się,  iż  wśród 
użytkowników języka C++ panuje powszech-
ne  przekonanie,  iż  statyczne  asercje  to  jakiś 
ezoteryczny  mechanizm  przeznaczony  je-
dynie  dla  autorów  nowoczesnych  (tudzież 
skomplikowanych)  bibliotek  wykorzystują-
cych takie techniki jak metaprogramowanie 
oparte na szablonach czy programowanie ge-
neryczne.  W  niniejszym  artykule  postaram 
się rozwiać ten mit, pokazując, że statyczne 
asercje to bardzo prosty, a zarazem użytecz-
ny mechanizm, który z powodzeniem moż-
na  wykorzystać  w  zwyczajnych,  pisanych  na 
co dzień programach.

Czym jest statyczna asercja?

Mechanizmu asercji nie trzeba chyba przed-
stawiać nikomu, kto miał do czynienia z ję-
zykiem C bądź C++. Makro 

assert

 to bar-

dzo  proste,  a  jednocześnie  potężne  narzę-
dzie  programistyczne,  służące  do  weryfi-
kacji niezmienników w kodzie źródłowym. 
Poprzez  niezmiennik  należy  rozumieć  do-
wolny warunek, który w mniemaniu autora 
programu powinien być w określonym kon-
tekście spełniony. Jednym z najbardziej po-
pularnych zastosowań makra 

assert

 w języ-

ku C\C++ jest sprawdzanie, czy dany wskaź-
nik jest niezerowy:

assert( p != NULL );

Podobnych  warunków  może  być  nieskończe-
nie wiele. Niepisana zasada mówi, że asercje po-
winny  być  wykorzystywane  tylko  i  wyłącznie 
w  celu  wyłapywania  błędów  programisty.  Na 
przykład,  jeśli  programista  zakłada,  że  wskaź-
nik  przekazywany  do  funkcji  będzie  niezero-
wy,  lub  wartość  zwracana  z  innej  funkcji  bę-
dzie zawsze parzysta, to tego rodzaju niezmien-
niki można weryfikować (i dokumentować) za 
pomocą  asercji.  W  przypadku  gdy  użytkow-
nik wpisał niepoprawne dane lub system ope-
racyjny  nie  udostępnił  programowi  żądanego 
zasobu, należy użyć alternatywnych mechani-
zmów obsługi błędów, na przykład: rzucić wy-
jątek. Opisana wyżej zasada wiąże się poniekąd 
z rezultatami działania makra 

assert

 objawia-

jącymi się w przypadku, gdy przekazany do nie-
go warunek nie będzie spełniony. Niespełniona 
asercja spowoduje brutalne przerwanie progra-
mu i wypisanie informacji o błędzie (zazwyczaj 
podawana  jest  nazwa  pliku  z  kodem  źródło-
wym  oraz  numer  linii,  w  której  wystąpił  pro-
blem). Oczywistym jest, iż końcowy użytkow-
nik nie chciałby zobaczyć takiego komunikatu 
w  sytuacji  gdy  np.  wpisze  niepoprawnie  sfor-
matowany adres e-mail.

Makro 

assert

 ma pewną ważną właściwość: 

jest ono skonstruowane w taki sposób, że dzia-
ła  tylko  wtedy,  gdy  program  jest  zbudowany 
w  trybie  odpluskwiania  (ang.  debug  mode).  W 
programach  zbudowanych  w  trybie  produk-
cyjnym (ang. release mode) instrukcje 

assert

 są 

pominięte i nie powodują żadnego dodatkowe-
go narzutu.

Kolejną, ważną cechą konstrukcji 

assert

 jest 

fakt,  iż  działa  ona  w  czasie  wykonania  progra-
mu. W praktyce oznacza to, że warunki opisane 
we wszystkich asercjach będą sprawdzone przez 
program  dopiero  w  momencie,  kiedy  zawiera-
jący  je  kod  będzie  wykonywany.  Z  tego  wzglę-
du  makro 

assert

  nazywane  jest  czasami  aser-

cją dynamiczną.

No  dobrze...  –  stwierdzi  uważny  Czytelnik 

–  ale  czy  zawsze  potrzebujemy  korzystać  z  asercji 
dynamicznych?
  Odpowiedź  brzmi:  oczywiście, 
że  nie!  Przecież  –  myśląc  zdroworozsądkowo 
– niektóre założenia odnośnie działania progra-

Statyczne asercje w C++

Dynamiczne asercje to idiom powszechnie znany i stosowany, statyczne 

asercje  są  zdecydowanie  mniej  popularne.  Jeśli  chciałbyś  przekonać 

się,  do  czego  służy  ten  ciekawy  mechanizm,  zapraszam  do  lektury 

niniejszego artykułu. 

Dowiesz się:

•   Co to jest statyczna asercja w języku C++;
•   Jakie biblioteki oferują mechanizm statycz-

nej asercji;

•   Jak  własnoręcznie  zaimplementować  me-

chanizm statycznej asercji.

Powinieneś wiedzieć:

•   Podstawy programowania w języku C++.

Poziom 

trudności

Łatwe sprawdzanie warunków w czasie kompilacji

Listing 1. Zastosowanie statycznej asercji 

dostępnej w ramach pakietu Boost

#include <limits>
#include <boost/static_assert.hpp>

template

class

 

UInt

 >

class

 

Test

{

private

:

    

BOOST_STATIC_ASSERT

(

        

(

 

std

::

numeric_limits

UInt

 >::

digits

 >= 

16

 

)

        && 

std

::

numeric_limits

UInt

 >:

:

is_specialized

        && 

std

::

numeric_limits

UInt

 >:

:

is_integer

        && !

std

::

numeric_limits

UInt

 

>::

is_signed

 

);

public

:

   

/* ... */

};

Listing 2. Przykładowe wykorzystanie 

mechanizmu standardowej statycznej asercji

template

 < 

class

 

T

 >

class

 

Test

{

private

:

    

static_assert

(

 

sizeof

(

 

int

 

)

 <= 

sizeof

(

 

T

 

),

                   

"T is not big 

enough!"

 

);

};

background image

11/2009

48

Perełki C++

Statyczne asercje w C++

www.sdjournal.org

49

mu znane są już na etapie kompilacji. Powstaje 
więc pytanie: czy nie dałoby się sprawdzać tych 
warunków właśnie wtedy?

Kluczem do znalezienia odpowiedzi na te py-

tania są właśnie statyczne asercje. Mechanizm ten 
działa  bardzo  podobnie  do  swojego  dynamicz-
nego  odpowiednika,  tyle  że  działa  na  etapie 
kompilacji programu.

Zaraz, zaraz... – zastanowi się ponownie uważ-

ny Czytelnik. Ale które warunki można weryfiko-
wać za pomocą statycznych asercji?
 Pytanie to jest 
niewątpliwie bardzo ważne. Odpowiedź brzmi: 
statyczne asercje obsługują tylko te warunki, któ-
re są zbudowane z tzw. całkowitych wyrażeń sta-
łych (ang. integral constant expressions). Wyrażenia 
te mogą składać się z następujących składników:

•   literały całkowite;
•   wartości enumeracji;
•   stałe obiekty, które zostały zainicjowane w 

miejscu definicji;

•   parametry szablonów będące liczbami cał-

kowitymi bądź wartościami enumeracji;

•   wyrażenia 

sizeof

;

•   stałe adresy.

W  kontekście  naszych  rozważań  na  temat  sta-
tycznych asercji istotny jest fakt, iż standard języ-
ka C++ narzuca wymóg mówiący o tym, że wy-
rażenia złożone z całkowitych wyrażeń stałych 
muszą być ewaluowane na etapie kompilacji pro-
gramu. Dzięki temu statyczne asercje mogą dzia-
łać. Tyle teorii. Czas na odrobinę praktyki.

Dostępne implementacje 

mechanizmu statycznych asercji

Wiemy już, czym jest w teorii statyczna asercja, 
czas wypróbować to narzędzie w działaniu. Za-
nim  jednak  drogi  Czytelniku  zaczniesz  zasta-
nawiać się, jak można zaimplementować opisa-
ny wyżej mechanizm, wstrzymaj się przez mo-
ment  –  najprawdopodobniej  zamiast  tego  bę-
dziesz mógł skorzystać z gotowej, wypróbowanej 
implementacji statycznej asercji. W niniejszym 
podrozdziale przedstawię dostępne alternatywy 
w tym zakresie.

Implementacja,  do  której  stosowania  będę 

Cię  namawiał  w  pierwszej  kolejności,  stanowi 
część zestawu bibliotek języka C++ znanych ja-
ko Boost (patrz: ramka W Sieci). Boost to narzę-
dzie wypróbowane i dobrze udokumentowane 
– korzystając z niego, masz gwarancję wysokiej 
jakości, przenośności oraz niezawodności kodu. 
Mechanizm statycznej asercji jest dostępny w ra-
mach biblioteki Boost.StaticAssert (patrz: ram-
ka W Sieci) pod postacią makra 

BOOST_STATIC_

ASSERT

. W celu skorzystania z tej biblioteki na-

leży dołączyć nagłówek static_assert.hpp. Na Li-
stingu 1 przedstawiony jest przykład zastosowa-
nia statycznej asercji rodem z Boost.

W  tym  przypadku  sprawdzamy,  czy  para-

metr 

UInt

 w szablonie 

Test

 jest typu całkowite-

go ze znakiem i jest w stanie reprezentować cyfrę 

zapisaną na co najmniej 16 bitach (nie licząc bi-
tu reprezentującego znak).

Warto w tym miejscu wspomnieć, iż makro 

BOOST_STATIC_ASSERT

  może  być  użyte  zarów-

no w zakresie przestrzeni nazw, funkcji, szablo-
nu funkcji, klasy oraz szablonu klasy.

Alternatywą dla zestawu bibliotek Boost (w 

kontekście statycznych asercji) może być biblio-
teka Loki (patrz: ramka W Sieci). Biblioteka ta 
związana  jest  z  książką  autorstwa  Andrei  Ale-
xandrescu pt. Nowoczesne Projektowanie w C++ 
(tytuł  angielski:  Modern  C++  Design).  Pozycja 
ta, uznawana w kręgu programistów C++ za kla-
sykę, opisuje szczegółowo szereg nowoczesnych 
technik  języka  C++:  między  innymi  statyczne 
asercje. Zaprezentowane w książce mechanizmy 
zaimplementowane są w towarzyszącej bibliote-
ce: Loki. Statyczna asercja rodem z Loki dostar-
czona jest w postaci makra 

LOKI_STATIC_CHECK

zaś ogólny schemat korzystania z niej jest iden-
tyczny jak w przypadku 

BOOST_STATIC_ASSERT

Biblioteka Loki zawiera w sobie wiele interesują-
cych, wartych uwagi rozwiązań, aczkolwiek nie 
jest ona tak aktywnie rozwijana jak Boost – dla-
tego też, jako domyślną implementację mecha-
nizmu  statycznej  asercji,  polecam  zdecydowa-
nie Boost.StaticAssert.

Bardzo  interesującą  alternatywą  dla  wspo-

mnianych  wyżej  bibliotek  jest...  standard 
C++0x.  W  planowanej,  nowej  odsłonie  języ-
ka  C++  jego  projektanci  przewidzieli  statycz-
ne asercje jako wbudowany mechanizm języka 
w  postaci  słowa  kluczowego 

static_assert

Składnia  standardowej  statycznej  asercji  wyglą-
da następująco:

static_assert( stałe_wyrażenie, komunikat_

błędu );

Przykładowe wykorzystanie tego mechanizmu 
przedstawione jest na Listingu 2.

Na nowy standard języka C++ przyjdzie nam 

zapewne  jeszcze  trochę  poczekać,  aczkolwiek 
zadowalające jest to, że twórcy kompilatorów nie 
zasypują gruszek w popiele i już dziś, w najnow-
szych wersjach swoich produktów, oferują pro-
gramistom wybrane właściwości C++0x. W mo-
mencie pisania niniejszego artykułu standardo-
wy mechanizm statycznych asercji dostępny jest 
w Visual Studio 2010 CTP oraz w GCC (wersja 
4.3 oraz wyższe). Odnośniki do wspomnianych 
narzędzi można znaleźć w ramce W Sieci. Zain-
teresowanych  Czytelników  gorąco  namawiam 
do eksperymentów z tymi kompilatorami.

Własna implementacja 

mechanizmu statycznej asercji

Mogą zdarzyć się przypadki, kiedy programista 
nie może użyć istniejącej implementacji statycz-
nej asercji. Powody bywają różne: opisane wyżej 
biblioteki  mogą  być  niedostępne  na  docelową 
platformę,  tudzież  polityka  firmy  realizującej 
dany projekt może nie zezwalać na stosowanie 

zewnętrznych rozwiązań. W takim przypadku 
pozostaje jedynie zakasać rękawy i zaimplemen-
tować mechanizm statycznej asercji własnoręcz-
nie. Wbrew pozorom, nie wymaga to wiele wy-
siłku: przykładowa implementacja (którą z po-
wodzeniem można nazwać rozwiązaniem pro-
dukcyjnym) pokazana jest na Listingu 3.

Treść  wspomnianego  Listingu  powinna  być 

zapisana  w  pliku  nagłówkowym  (np.  StaticAs-
sert.hpp
);  w  takiej  postaci  będzie  gotowa  do 
użycia.  Nasza  implementacja  statycznej  aser-
cji  opiera  się  na  specjalizacjach  szablonu  kla-
sy 

StaticAsserter

.  Szablon  ten  przyjmuje  je-

den parametr typu 

bool

 i  zawiera jedynie pu-

Listing 3. Implementacja mechanizmu 

statycznej asercji

#if !defined __STATIC_ASSERT_HPP_

INCLUDED__

#define __STATIC_ASSERT_HPP_INCLUDED__
#if !defined _DEBUG
#define static_assert( expr, msg )
#else 

// !defined _DEBUG

namespace

 

Private

{

    

template

bool

 > 

struct

 

StaticAsserter

    

{

        

StaticAsserter

(

 

...

 

)

 

{}

    

};

    

template

<> 

struct

 

StaticAsserter

      

false

 >

;

}

#define static_assert( expr, msg ) \
{\
    class ERROR_##msg {}; \
    Private::StaticAsserter< ( expr ) 
      != 0 >( ( ERROR_##msg ) ); \
}
#endif 

// !defined _DEBUG

#endif 

// __STATIC_ASSERT_HPP_

INCLUDED__

Listing 4. Test prezentowanej implementacji 

mechanizmu statycznej asercji

#define _DEBUG
#include "StaticAssert.hpp"

int

 

main

()

{

    

const

 

int

 

a

 = 

63

;

    

const

 

int

 

b

 = -

1

;

    

static_assert

(

 

(

 

a

 % 

2

 

)

 == 

0

,

                   

A_is_not_

dividable_by_2

 

);

    

static_assert

(

 

b

 >= 

0

,

                   

B_must_be_greater_

or_equal_zero

 

);

    

return

 

0

;

}

background image

11/2009

50

Perełki C++

sty konstruktor, który przyjmuje argumenty do-
wolnego typu (w tym celu stosujemy wielokro-
pek) – za moment wyjaśnię dlaczego. Specjali-
zacja tego szablonu dla wartości parametru rów-
nej 

false

 jest tylko zadeklarowana. Szablon jest 

umieszczony w przestrzeni nazw 

Private

 (nie 

jest to konieczne, ale ja stosuję taki zabieg jako 
wskazówkę mówiącą, że zawarty tutaj kod sta-
nowi szczegół implementacji i nie powinien być 
wykorzystywany przez kod kliencki).

Rzućmy  okiem  na  definicję  makra 

static_

assert

.  Na  początku  definiujemy  temporalną 

klasę reprezentującą komunikat o błędzie:

class ERROR_##msg {};

W  dalszej  kolejności  tworzona  jest  instancja 
szablonu 

StaticAsserter

, przy czym jako pa-

rametr tego szablonu podawane jest wyrażenie 

( expr ) != 0

expr

 musi być oczywiście cał-

kowitym  wyrażeniem  stałym,  aby  kompilator 
mógł  obliczyć  je  w  czasie  kompilacji.  Jeśli  wy-
rażenie  to  jest  niezerowe,  to  kompilator  prze-
kształci  je  na  wartość 

true

  i  użyje  podstawo-

wej  definicji  szablonu  (tej  z  wielokropkiem  w 
konstruktorze).  Konstruktor  tej  klasy  przyj-
muje...  cokolwiek,  więc  bezproblemowo  prze-
łknie  obiekt  zdefiniowanej  wcześniej  tymcza-
sowej  klasy.  W  rezultacie  nie  wydarzy  się  nic. 
I o to chodzi! Ciekawe rzeczy zaczną się dziać 
w  sytuacji,  kiedy  wyrażenie  w  warunku  bę-
dzie równe 

0

: wtedy kompilator użyje specjali-

zacji szablonu klasy 

StaticAsserter

 dla para-

metru równego 

false

. Specjalizacja ta jest nie-

zdefiniowana,  więc  w  rezultacie  otrzymamy 
komunikat o błędzie, który będzie zawierał nu-
mer linii, w której ten błąd wystąpił (wskazują-
cy,  gdzie  umieściliśmy  statyczną  asercję)  oraz 
– nazwę temporalnej klasy (reprezentującą ko-
munikat o błędzie). Voila! Mamy wszystko cze-
go nam potrzeba!

Oczywiście,  wystarczy  zmienić  wartości 

stałych 

a

 i 

b

 , przykładowo na: 

64

 i 

1

, a kom-

pilacja przejdzie bezbłędnie. Gdyby zachciało 
nam się oszukać kompilator i np. usunąć sło-
wo kluczowe 

const 

z definicji stałej 

a

, reak-

cja obydwu narzędzi będzie natychmiastowa. 
Dla przykładu, w komunikacie o błędzie wy-

generowanym przez kompilator GCC pojawi 
się informacja:
StaticAssertTest.cpp:10: error: `a’ cannot appear in 
a constant-expression

Statyczne asercje: studium zastosowań
Wiemy już, co to jest statyczna asercja, jak ją 
stosować, a nawet – jak ją zaimplementować. 
Pozostaje  ostatnie  –  chyba  najbardziej  klu-
czowe pytanie: w jakim celu, tudzież w jakich 
przypadkach stosować ten mechanizm. W ni-
niejszym podpunkcie postaram się na to pyta-
nie odpowiedzieć.

Pierwsza,  najbardziej  chyba  oczywista 

wskazówka odnośnie stosowania statycznych 
asercji brzmi: za każdym razem, kiedy wsta-
wiasz w kodzie dynamiczną asercję, sprawdź, 
czy  wyrażenie  warunkowe  nie  jest  przypad-
kiem  całkowitym  wyrażeniem  stałym.  Jeśli 
nie, to rozważ, czy nie da się przekształcić go 
na takie wyrażenie.

Pozwolę sobie przytoczyć w tym miejscu przy-

kład z mojej własnej praktyki. Miałem okazję pra-
cować niedawno nad implementacją gry bazują-
cej na tzw. kafelkach (ang. tiles). Mechanizm ka-
felkowania przy programowaniu gier oznacza, że 
wybrane elementy sceny gry (np. tło) składane są 
przy renderowaniu z ograniczonej liczby elemen-
tów podstawowych. Element taki, zwany kaflem 
(ang. tile), to zazwyczaj prostokątny fragment ob-
razu, który kopiuje się na ekran. W moim przy-
padku szerokość i wysokość kafla były stałe. Zde-
finiowałem je w mniej więcej następujący sposób:

const int k_tileWidth = 32;
const int k_tileHeight = 32;

Oprócz  tego,  przyjąłem  założenie,  iż  wielko-
ści  te  muszą  być  podzielne  przez  liczbę  8;  na 
tym założeniu opierał się algorytm przewijania 
planszy gry. Zacząłem się zastanawiać, jak wy-
musić spełnienie tego warunku – na wypadek 
gdybym  chciał  w  przyszłości  zmienić  rozmiar 
kafelka.  Asercja  wydała  mi  się  idealnym  roz-
wiązaniem,  szybko  jednak  zdałem  sobie  spra-

wę,  że  warunek  ten  można  zweryfikować  na 
etapie kompilacji mojego programu. W efekcie 
napisałem kod podobny do tego, który pokaza-
ny jest na Listingu 5.

Oczywiście, poprzestając jedynie na próbach 

przekształcania dynamicznych asercji na ich sta-
tyczne odpowiedniki, nie uda nam się wykorzy-
stać  pełnego  potencjału  nowo  poznanego  na-
rzędzia.  Aby  okiełznać  moc  statycznych  aser-
cji,  warto  zainteresować  się  ciekawym  mecha-
nizmem,  służącym  do  badania  typów  oraz  re-
lacji  występujących  między  nimi.  Mowa  tutaj 
o tzw. cechach typów (ang. type traits). Mecha-
nizm  ten,  dostępny  w  nowoczesnych  bibliote-
kach  C++  (np.  wspomniane  wcześniej  Boost  i 
Loki),  pozwala  sprawdzać  w  czasie  kompilacji 
niebanalne cechy typów, np.: czy dany typ jest 
typem  bazowym  dla  innego  typu,  czy  posiada 
konstruktor  kopiujący  czy  może  jest  wskaźni-
kiem  do  metody  klasy.  Czytelników  zaintere-
sowanych  możliwościami  tego  narzędzia  odsy-
łam do dokumentacji biblioteki Boost.TypeTra-
its (patrz: ramka W Sieci). W kontekście niniej-
szego artykułu najbardziej istotne jest to, że ce-
chy typów można testować na etapie kompilacji, 
a co za tym idzie – nakładać na nie ograniczenia 
przy pomocy statycznych asercji!

Statyczne  asercje  będą  niewątpliwie  nieoce-

nioną pomocą dla osób piszących programy nie-
zależne  od  platformy:  makro 

static_assert

 

nieraz  może  uratować  je  przed  trudnymi  do 
wykrycia  błędami  wynikającymi  z  niezgodno-
ści reprezentacji typów stosowanych przez róż-
ne kompilatory.

Reasumując podpunkt o zastosowaniach sta-

tycznych asercji, można by pokusić się o stwier-
dzenie: grunt to zacząć ich używać, a wtedy oka-
zje do ich stosowania zaczną pojawiać się same.

Podsumowanie

W  powyższym  artykule  przedstawiłem  defini-
cję  mechanizmu  statycznych  asercji,  zaprezen-
towałem  najpopularniejsze  jego  implementa-
cje, opisałem implementację własną, zaś na koń-
cu – omówiłem krótkie studium użycia tego po-
żytecznego narzędzia. Gorąco zachęcam Czytel-
ników do stosowania zaprezentowanego idiomu 
w celu usprawnienia własnych programów.

Listing 5. Zastosowanie statycznych asercji do 

wymuszenia odpowiednich rozmiarów kafelka

static_assert

(

 

(

 

k_tileWidth

 % 

8

 

)

 

== 

0

,

              

Tile_width_is_not_

dividable_by_8

 

);

static_assert

(

 

(

 

k_tileHeight

 % 

8

 

)

 

== 

0

,

               

Tile_height_is_not_

dividable_by_8

 

);

W Sieci

•   http://www.boost.org/ – strona domowa 

pakietu bibliotek Boost;

•   http://www.boost.org/doc/libs/1_40_0/

doc/html/boost_staticassert.html – doku-

mentacja biblioteki Boost.StaticAssert;

•   http://loki-lib.sourceforge.net/  –  strona 

domowa biblioteki Loki;

•   ht t ps : //co n n e c t . m i c roso f t . co m/

VisualStudio/content/content.aspx?Conte

ntID=9790 – strona domowa Visual Stu-

dio 2010 CTP;

•   http://gcc.gnu.org/  –  strona  domowa 

projektu GCC;

•   http://www.boost.org/doc/libs/1_40_0/

libs/type_traits/doc/html/index.html – do-

kumentacja biblioteki Boost.TypeTraits.

RAFAŁ KOCISZ

Pracuje  na  stanowisku  Dyrektora  Technicznego  w 
firmie Gamelion, wchodzącej w skład Grupy BLStre-
am. Rafał specjalizuje się w technologiach związa-
nych  z  produkcją  oprogramowania  na  platformy 
mobilne,  ze  szczególnym  naciskiem  na  tworzenie 
gier. Grupa BLStream powstała, by efektywniej wy-
korzystywać potencjał dwóch szybko rozwijających 
się  producentów  oprogramowania  –  BLStream  i 
Gamelion. Firmy wchodzące w skład grupy specjali-
zują się w wytwarzaniu oprogramowania dla klien-
tów  korporacyjnych,  w  rozwiązaniach  mobilnych 
oraz produkcji i testowaniu gier. 
Kontakt z autorem: rafal.kocisz@game-lion.com