2009 11 Statyczne asercje w C [Programowanie C C ]

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

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


Wyszukiwarka

Podobne podstrony:
2009 11 Klasy cech w programowaniu generycznym [Programowanie C C ]
2009 11 Sprytne wskaźniki [Programowanie C C ]
2009-11-05, pedagogium, wykłady, Teoria edukacji obronnej i bezpieczeństwa publicznego
2009 11 17 arduino basics
2009 11 Informatyka śledcza
Inżynieria 1-11, „Metodologia programowania” sem
2009.11.29 Podstawy żywienia 4-6 lat(S)
ZW 2009-11 03
2009 11 08 5 Finanse NBP,BFG, PBid 26674 ppt
2009-11-19, pedagogium, wykłady, Teoria edukacji obronnej i bezpieczeństwa publicznego
2009 11 03
2009 11 17
2009 11 30 rachunek i statystykaid 26677
2009 11 30 matematyka finansowaid 26676
2009 11 Audyt i kontrola danych osobowych
2009.11.20. Biofizyka, WSPiA, 1 ROK, Semestr 1, Biofizyka

więcej podobnych podstron