Darmowy fragment publikacji:
Wiêcej ni¿ C++.
Wprowadzenie do
bibliotek Boost
Autor: Björn Karlsson
T³umaczenie: Przemys³aw Szeremiota
ISBN: 83-246-0339-5
Tytu³ orygina³u: Beyond the C++ Standard Library:
An Introduction to Boost
Format: B5, stron: 384
Jêzyk C++ znajduje coraz wiêcej zastosowañ, w wypadku których biblioteka
standardowa czêsto okazuje siê zbyt uboga. Projekt Boost powsta³ w celu wype³nienia
luk i wyeliminowania niedoskona³oœci biblioteki STL. Dziœ biblioteki Boost zyskuj¹
coraz wiêksz¹ popularnoœæ, czego dowodem jest w³¹czenie dziesiêciu z nich do
przygotowywanej biblioteki standardowej jêzyka C++0x. Twórcy kolejnej specyfikacji
C++ zdecydowali siê nawet na kilka modyfikacji jêzyka w celu u³atwienia korzystania
z bibliotek Boost.
Ksi¹¿ka „Wiêcej ni¿ C++. Wprowadzenie do bibliotek Boost” to przegl¹d 58 bibliotek
projektu. Dwanaœcie z nich omówiono szczegó³owo i zilustrowano przyk³adami.
Analizuj¹c zaprezentowane projekty, przekonasz siê, jak bardzo biblioteki Boost
u³atwiaj¹ pracê i pozwalaj¹ ulepszyæ aplikacje. Nauczysz siê korzystaæ z inteligentnych
wskaŸników, obiektów funkcyjnych, wyra¿eñ regularnych i wielu innych funkcji
oferowanych przez biblioteki Boost.
(cid:129) Bezpieczna konwersja typów
(cid:129) Stosowanie elastycznych bibliotek kontenerów
(cid:129) Wyra¿enia regularne
(cid:129) Wywo³ania zwrotne
(cid:129) Zarz¹dzanie sygna³ami i slotami
Wykorzystaj ju¿ teraz elementy bibliotek Boost, a nowa biblioteka standardowa nie
bêdzie mia³a przed Tob¹ ¿adnych tajemnic.
IDZ DO
IDZ DO
PRZYK£ADOWY ROZDZIA£
PRZYK£ADOWY ROZDZIA£
SPIS TREŒCI
SPIS TREŒCI
KATALOG KSI¥¯EK
KATALOG KSI¥¯EK
KATALOG ONLINE
KATALOG ONLINE
ZAMÓW DRUKOWANY KATALOG
ZAMÓW DRUKOWANY KATALOG
TWÓJ KOSZYK
TWÓJ KOSZYK
DODAJ DO KOSZYKA
DODAJ DO KOSZYKA
CENNIK I INFORMACJE
CENNIK I INFORMACJE
ZAMÓW INFORMACJE
ZAMÓW INFORMACJE
O NOWOŒCIACH
O NOWOŒCIACH
ZAMÓW CENNIK
ZAMÓW CENNIK
CZYTELNIA
CZYTELNIA
FRAGMENTY KSI¥¯EK ONLINE
FRAGMENTY KSI¥¯EK ONLINE
Wydawnictwo Helion
ul. Chopina 6
44-100 Gliwice
tel. (32)230-98-63
e-mail: helion@helion.pl
Słowo wstępne ................................................................................. 9
Od autora ....................................................................................... 11
Podziękowania ................................................................................ 13
O autorze ....................................................................................... 15
Organizacja materiału ..................................................................... 17
Przegląd bibliotek Boost ................................................................. 19
Przetwarzanie tekstów i ciągów znaków ..........................................................................19
Struktury danych, kontenery, iteratory i algorytmy .........................................................21
Obiekty funkcyjne i programowanie wyższego rzędu .....................................................24
Programowanie uogólnione i metaprogramowanie z użyciem szablonów ......................26
Liczby i obliczenia ...........................................................................................................29
Wejście-wyjście ...............................................................................................................31
Różne ................................................................................................................................32
Część I Biblioteki ogólnego przeznaczenia ..................................37
Rozdział 1. Biblioteka Smart_ptr ....................................................................... 39
Jak ulepszyć własne programy z użyciem biblioteki Smart_ptr? ....................................39
Po co nam inteligentne wskaźniki? ..................................................................................40
Jak ma się biblioteka Smart_ptr do biblioteki standardowej C++? ..................................41
scoped_ptr ........................................................................................................................42
scoped_array .....................................................................................................................50
shared_ptr .........................................................................................................................51
shared_array .....................................................................................................................63
intrusive_ptr .....................................................................................................................63
weak_ptr ...........................................................................................................................74
Smart_ptr — podsumowanie ............................................................................................82
Rozdział 2. Biblioteka Conversion ..................................................................... 83
Jak ulepszyć własne programy z użyciem biblioteki Conversion? ..................................83
polymorphic_cast .............................................................................................................84
polymorphic_downcast ....................................................................................................90
numeric_cast .....................................................................................................................93
lexical_cast .....................................................................................................................100
Conversion — podsumowanie .......................................................................................105
D:\roboczy jarek\makiety poprawki i druku pdf\Więcej niż C++ Wprowadzenie do bibliotek Boost\07
druk\!!Spis.doc
5
6
Spis treści
Rozdział 3. Biblioteka Utility ........................................................................... 107
Jak ulepszyć własne programy z użyciem biblioteki Utility? ........................................107
BOOST_STATIC_ASSERT ..........................................................................................108
checked_delete ...............................................................................................................110
noncopyable ...................................................................................................................114
addressof .........................................................................................................................119
enable_if .........................................................................................................................121
Utility — podsumowanie ...............................................................................................129
Rozdział 4. Biblioteka Operators ..................................................................... 131
Jak ulepszyć własne programy z użyciem biblioteki Operators? ...................................131
Jak ma się biblioteka Operators do biblioteki standardowej C++? ................................132
Operators ........................................................................................................................132
Stosowanie .....................................................................................................................137
Operators — podsumowanie ..........................................................................................156
Rozdział 5. Biblioteka Regex .......................................................................... 157
Jak ulepszyć własne programy z użyciem biblioteki Regex? ........................................157
Jak ma się biblioteka Regex do biblioteki standardowej C++? .....................................158
Regex ..............................................................................................................................158
Stosowanie ..................................................................................................................... 160
Regex — podsumowanie ...............................................................................................174
Część II Kontenery i struktury danych .......................................175
Rozdział 6. Biblioteka Any .............................................................................. 177
Jak ulepszyć własne programy z użyciem biblioteki Any? ............................................177
Jak ma się biblioteka Any do biblioteki standardowej C++? .........................................178
Any .................................................................................................................................178
Stosowanie .....................................................................................................................181
Any — podsumowanie ...................................................................................................203
Rozdział 7. Biblioteka Variant ......................................................................... 205
Jak ulepszyć własne programy z użyciem biblioteki Variant? ......................................205
Jak ma się biblioteka Variant do biblioteki standardowej C++? ....................................206
Variant ............................................................................................................................206
Stosowanie .....................................................................................................................209
Variant — podsumowanie ..............................................................................................219
Rozdział 8. Biblioteka Tuple ........................................................................... 221
Jak ulepszyć własne programy z użyciem biblioteki Tuple? .........................................221
Jak ma się biblioteka Tuple do biblioteki standardowej C++? ......................................222
Tuple ...............................................................................................................................222
Stosowanie .....................................................................................................................227
Tuple — podsumowanie ................................................................................................243
Część III Obiekty funkcyjne i programowanie wyższego rzędu .....245
Rozdział 9. Biblioteka Bind ............................................................................. 247
Jak ulepszyć własne programy z użyciem biblioteki Bind? ...........................................247
Jak ma się biblioteka Bind do biblioteki standardowej C++? ........................................248
Bind ................................................................................................................................248
Stosowanie .....................................................................................................................249
Bind — podsumowanie ..................................................................................................273
6 D:\roboczy jarek\makiety poprawki i druku pdf\Więcej niż C++ Wprowadzenie do bibliotek Boost\07 druk\!!Spis.doc
Spis treści
7
Rozdział 10. Biblioteka Lambda ........................................................................ 275
Jak ulepszyć własne programy z użyciem biblioteki Lambda? .....................................275
Jak ma się biblioteka Lambda do biblioteki standardowej języka C++? .......................276
Lambda ...........................................................................................................................277
Stosowanie .....................................................................................................................278
Lambda — podsumowanie .............................................................................................312
Rozdział 11. Biblioteka Function ....................................................................... 313
Jak ulepszyć własne programy z użyciem biblioteki Function? ....................................313
Jak ma się biblioteka Function do biblioteki standardowej języka C++? ......................313
Function ..........................................................................................................................314
Stosowanie .....................................................................................................................317
Function — podsumowanie ...........................................................................................337
Rozdział 12. Biblioteka Signals ......................................................................... 339
Jak ulepszyć własne programy z użyciem biblioteki Signals? .......................................339
Jak ma się biblioteka Signals do biblioteki standardowej języka C++? ........................340
Signals ............................................................................................................................340
Stosowanie .....................................................................................................................343
Signals — podsumowanie ..............................................................................................365
Bibliografia ................................................................................... 367
Skorowidz ..................................................................................... 371
D:\roboczy jarek\makiety poprawki i druku pdf\Więcej niż C++ Wprowadzenie do bibliotek Boost\07
druk\!!Spis.doc
7
Rozdział 1.
Jak ulepszyć własne programy
z użyciem biblioteki Smart_ptr?
t
Poprzez automatyczne zarządzanie czasem życia obiektów za pomocą
szablonu shared_ptr, bezpiecznie i efektywnie zarządzającego wspólnymi
zasobami.
t
Poprzez bezpieczne podglądanie zasobów wspólnych za pomocą szablonu
weak_ptr, co eliminuje ryzyko charakterystyczne dla wiszących wskaźników.
t
Poprzez osadzanie zasobów w zasięgach programu za pomocą szablonów
scoped_ptr i scoped_array, ułatwiających konserwację kodu i pomocnych
przy zabezpieczaniu przed zgubnym wpływem wyjątków.
Wskaźniki inteligentne, implementowane w bibliotece Smart_ptr, rozwiązują odwiecz-
ny problem zarządzania czasem życia zasobów (chodzi zwykle o zasoby przydzielane
dynamicznie1). Inteligentne wskaźniki dostępne są w wielu odmianach. Wszystkie
jednak mają jedną cechę wspólną: automatyzację zarządzania zasobami. Ów auto-
matyzm manifestuje się rozmaicie, na przykład poprzez kontrolę czasu życia obiek-
tów przydzielanych dynamicznie czy też poprzez kontrolę nad akwizycją i zwalnia-
niem zasobów (plików, połączeń sieciowych itp.). Wskaźniki inteligentne z biblioteki
Boost implementują pierwsze z tych zastosowań, to jest przechowują wskaźniki do
dynamicznie przydzielanych obiektów, dbając o ich zwalnianie w odpowiednich mo-
mentach. Można się zastanawiać, czy to nie zbytnie ograniczenie ich zadań. Czy nie
można by zaimplementować również pozostałych aspektów zarządzania zasobami?
Cóż, można by, ale nie za darmo. Rozwiązania ogólne wymagają często większej zło-
żoności, a przy inteligentnych wskaźnikach biblioteki Boost główny nacisk położono
nawet nie tyle na elastyczność, co na wydajność. Ale dzięki możliwości implementowania
Czyli wszelkie zasoby, do których można się odwoływać za pośrednictwem typu wskaźnikowego
1
— również inteligentnego — przyp. aut.
D:\roboczy jarek\makiety poprawki i druku pdf\Więcej niż C++ Wprowadzenie do bibliotek Boost\07 druk\r01-
06.doc
37
38
Część I ¨ Biblioteki ogólnego przeznaczenia
własnych mechanizmów zwalniania najbardziej inteligentne ze wskaźników z rodziny
Boost (boost::shared_ptr) mogą obsługiwać zasoby wymagające przy zwalnianiu
bardziej wyrafinowanych operacji niż proste wywołanie delete. Pięć implementacji
wskaźników inteligentnych w bibliotece Boost.Smart_ptr odzwierciedla zaś szereg
kategorii potrzeb pojawiających się w programowaniu.
Po co nam inteligentne wskaźniki?
Po co nam inteligentne wskaźniki?
Wskaźniki inteligentne stosuje się przy:
t
manipulowaniu zasobami pozostającymi w posiadaniu wielu obiektów,
t
pisaniu kodu odpornego na wyjątki,
t
unikaniu typowych błędów w postaci wycieków zasobów.
Współdzielenie własności zachodzi, kiedy dany obiekt jest użytkowany przez pewną
liczbę innych obiektów. Jak (albo raczej: kiedy) należy zwolnić ów używany obiekt?
Aby rozpoznać odpowiedni moment zwolnienia współużytkowanego obiektu, nale-
żałoby wyposażyć każdy z obiektów użytkujących w informacje o współwłaścicielach.
Tego rodzaju wiązanie obiektów nie jest pożądane z punktu widzenia poprawności
projektowej, a także z uwagi na łatwość konserwacji kodu. Lepiej byłoby, aby obiek-
ty-współwłaściciele złożyły odpowiedzialność za zarządzanie czasem życia współużyt-
kowanego obiektu na inteligentny wskaźnik. Ten, po wykryciu, że nie ma już żadnego
właściciela, może bezpiecznie zwolnić obiekt użytkowany.
Odporność na wyjątki to w najprostszym ujęciu zabezpieczenie przed wyciekami za-
sobów, jak również zabezpieczenie trwałości niezmienników programu w obliczu
wyjątków. Obiekt przydzielony dynamicznie może w obliczu wyjątku nie zostać zwol-
niony. W ramach procedury zwijania stosu przy zrzucaniu wyjątku i porzucaniu bie-
żącego zasięgu dojdzie do utracenia wskaźników obiektów dynamicznych, co uniemoż-
liwi zwolnienie obiektu aż do momentu zakończenia programu (a i w fazie końcowej
programu język nie daje gwarancji zwolnienia zasobów). Program niezabezpieczony
przed takim wpływem wyjątków może nie tylko doprowadzić do deficytu pamięci
operacyjnej, ale i znaleźć się w niestabilnym stanie; zastosowanie wskaźników inteli-
gentnych automatyzuje zwalnianie zasobów nawet w obliczu wyjątków.
Co do unikania typowych błędów, to najbardziej typowym jest chyba pominięcie (wyni-
kające z przeoczenia) wywołania delete. Tymczasem typowy inteligentny wskaźnik
nie śledzi bynajmniej ścieżek przebiegu wykonania programu — jego jedyną troską
jest zwolnienie wskazywanego obiektu wywołaniem delete w ramach własnej de-
strukcji. Stosowanie inteligentnych wskaźników zwalnia więc programistę od ko-
nieczności śledzenia pożądanych momentów zwalniania obiektów. Do tego inteligentne
wskaźniki mogą ukrywać szczegóły dealokacji, dzięki czemu klienci nie muszą wie-
38D:\roboczy jarek\makiety poprawki i druku pdf\Więcej niż C++ Wprowadzenie do bibliotek Boost\07 druk\r01-06.doc
Rozdział 1. ¨ Biblioteka Smart_ptr
39
dzieć, kiedy wywoływać delete, kiedy specjalną funkcję zwalniającą, a kiedy w ogóle
powstrzymać się od zwalniania zasobu.
Bezpieczne i efektywne wskaźniki inteligentne to ważna broń w arsenale programisty.
Choć biblioteka standardowa języka C++ udostępnia szablon std::auto_ptr, jego im-
plementacja nie spełnia wymienionych postulatów funkcjonalności inteligentnego
wskaźnika. Wskaźniki auto_ptr nie mogą na przykład występować w roli elementów
kontenerów biblioteki STL. Lukę w standardzie wypełniają z powodzeniem klasy in-
teligentnych wskaźników z biblioteki Boost.
W niniejszym rozdziale skupimy się na klasach scoped_ptr, shared_ptr, intrusive_ptr
i weak_ptr. Uzupełniające ten zestaw klasy scoped_array i shared_array, choć też
przydatne, nie są tak często potrzebne; do tego ich podobieństwo do pozostałych im-
plementacji wskaźników uzasadnia mniejszy poziom szczegółowości omówienia.
Jak ma się biblioteka Smart_ptr
do biblioteki standardowej C++?
Biblioteka Smart_ptr została zaproponowana do wcielenia do biblioteki standardowej.
Propozycja jest uzasadniona trojako:
t
Biblioteka standardowa języka C++ oferuje obecnie jedynie klasę auto_ptr,
pokrywającą zaledwie wąski wycinek spektrum zastosowań inteligentnych
wskaźników. Zwłaszcza w porównaniu do klasy shared_ptr, udostępniającej
odmienne, a jakże ważne udogodnienia.
t
Wskaźniki inteligentne biblioteki Boost zostały zaprojektowane jako
uzupełnienie i naturalne rozszerzenie biblioteki standardowej. Na przykład
przed zaproponowaniem shared_ptr nie istniały standardowe wskaźniki
inteligentne nadające się do użycia w roli elementów standardowych
kontenerów.
t
Programiści ustanowili inteligentne wskaźniki biblioteki Boost standardem
de facto, powszechnie i z powodzeniem wdrażając je we własnych
programach.
Wymienione względy sprawiają, że biblioteka Smart_ptr stanowi bardzo pożądany
dodatek do biblioteki standardowej języka C++. Klasy shared_ptr (i szablon pomocni-
czy enable_shared_from_this) i weak_ptr z Boost.Smart_ptr zostały więc zaakcepto-
wane do najbliższego raportu technicznego biblioteki standardowej, czyli dokumentu
zbierającego propozycje dla najbliższego wydania standardu opisującego bibliotekę
języka C++.
D:\roboczy jarek\makiety poprawki i druku pdf\Więcej niż C++ Wprowadzenie do bibliotek Boost\07 druk\r01-
06.doc
39
40
Część I ¨ Biblioteki ogólnego przeznaczenia
scoped_ptr
Nagłówek: boost/scoped_ptr.hpp
Szablon boost::scoped_ptr służy do zapewniania właściwego usuwania przydziela-
nego dynamicznie obiektu. Cechy klasy scoped_ptr upodobniają ją do std::auto_ptr,
z tą istotną różnicą, że w scoped_ptr nie zachodzi transfer prawa własności, charakte-
rystyczny dla auto_ptr. W rzeczy samej, wskaźnik scoped_ptr nie może być kopio-
wany ani przypisywany! Wskaźnik scoped_ptr gwarantuje zachowanie wyłącznego
posiadania obiektu wskazywanego, uniemożliwiając przypadkowe ustąpienie własności.
Ta własność scoped_ptr pozwala na wyraziste rozgraniczenie tej różnicy w kodzie
poprzez stosowanie raz scoped_ptr, a raz auto_ptr, zależnie od potrzeb.
Wybierając pomiędzy std::auto_ptr a boost::scoped_ptr, należy rozważyć właśnie
to, czy pożądaną cechą tworzonego inteligentnego wskaźnika ma być transfer prawa
własności obiektu wskazywanego. Jeśli nie, najlepiej zastosować scoped_ptr. Jego
implementacja jest na tyle odchudzona, że jego wybór nie spowoduje ani rozrostu, ani
spowolnienia programu — wpłynie za to korzystnie na bezpieczeństwo kodu i jego
zdatność do konserwacji.
Pora na przegląd składni scoped_ptr, uzupełniony krótkim opisem poszczególnych
składowych.
namespace boost {
template typename T class scoped_ptr : noncopyable {
public:
explicit scoped_ptr(T* p = 0);
~scoped_ptr();
void reset(T* p = 0);
T operator*() const;
T* operator- () const;
T* get() const;
void swap(scoped_ptr b);
};
template typename T
void swap(scoped_ptr T a, scoped_ptr T b);
}
Metody
explicit scoped_ptr(T* p = 0);
Konstruktor przechowujący kopię p. Uwaga: p musi być przydzielone za pośrednictwem
wywołania operatora new albo mieć wartość pustą (ang. null). T nie musi być w czasie
40D:\roboczy jarek\makiety poprawki i druku pdf\Więcej niż C++ Wprowadzenie do bibliotek Boost\07 druk\r01-06.doc
Rozdział 1. ¨ Biblioteka Smart_ptr
41
konstrukcji wskaźnika kompletnym typem. To przydatne, kiedy wskaźnik p jest wy-
nikiem wywołania pewnej funkcji przydziału, a nie bezpośredniego wywołania new:
skoro typ nie musi być kompletny, wystarcza deklaracja zapowiadająca T. Konstruk-
tor nie zrzuca wyjątków.
~scoped_ptr();
Usuwa obiekt wskazywany. Typ T musi być kompletny przy usuwaniu. Jeśli wskaźnik
scoped_ptr nie przechowuje w czasie destrukcji żadnego zasobu, destruktor nie wy-
konuje żadnych operacji dealokacji. Destruktor nie zrzuca wyjątków.
void reset(T* p = 0);
Wyzerowanie (ang. reset) wskaźnika scoped_ptr oznacza zwolnienie pozostającego
w jego pieczy wskaźnika (o ile taki istnieje), a następnie przyjęcie na własność wskaź-
nika p. Zazwyczaj scoped_ptr przejmuje całkowicie zarządzanie czasem życia obiektu,
ale w rzadkich sytuacjach, kiedy trzeba zwolnić zasób jeszcze przed zwolnieniem obiektu
wskaźnika scoped_ptr albo trzeba przekazać pod jego opiekę zasób inny niż pierwotny.
Jak widać, metoda reset może się przydać, ale należy ją stosować wstrzemięźliwie
(zbyt częste stosowanie znamionuje niekiedy ułomności projektu). Metoda nie zrzuca
wyjątków.
T operator*() const;
Zwraca referencję obiektu wskazywanego przez wskaźnik przechowywany w obiek-
cie scoped_ptr. Ponieważ nie istnieje coś takiego jak referencje puste, wyłuskiwanie
za pośrednictwem tego operatora obiektu scoped_ptr zawierającego wskaźnik pusty
prowokuje niezdefiniowane zachowanie. Jeśli więc zachodzą wątpliwości co do war-
tości przechowywanego wskaźnika, należy skorzystać z metody get. Operator nie zrzu-
ca wyjątków.
T* operator- () const;
Zwraca przechowywany wskaźnik. Wywołanie tej metody na rzecz obiektu sco-
ped_ptr zawierającego wskaźnik pusty prowokuje niezdefiniowane zachowanie. Jeśli
nie ma pewności, czy wskaźnik jest pusty, czy nie, należy zastosować metodę get.
Operator nie zrzuca wyjątków.
T* get() const;
Zwraca przechowywany wskaźnik. Metodę get należy stosować z zachowaniem
ostrożności, a to z racji ryzyka związanego z manipulowaniem „gołym” wskaźnikiem.
Metoda get przydaje się jednak choćby do jawnego sprawdzenia, czy przechowywa-
ny wskaźnik jest pusty. Metoda nie zrzuca wyjątków. Wywołuje się ją typowo celem
zaspokojenia wymogów, np. wywołania funkcji wymagającej przekazania argumentu
w postaci zwykłego wskaźnika.
operator nieokreślony-typ-logiczny() const
Określa, czy scoped_ptr jest niepusty. Typ wartości zwracanej (bliżej nieokreślony) po-
winien nadawać się do stosowania w kontekście wymagającym wartości logicznych.
D:\roboczy jarek\makiety poprawki i druku pdf\Więcej niż C++ Wprowadzenie do bibliotek Boost\07 druk\r01-
06.doc
41
42
Część I ¨ Biblioteki ogólnego przeznaczenia
Tę funkcję konwersji można stosować zamiast metody get w instrukcjach warunko-
wych do testowania stanu wskaźnika scoped_ptr.
void swap(scoped_ptr b);
Wymienia zawartość dwóch obiektów klasy scoped_ptr. Nie zrzuca wyjątków.
Funkcje zewnętrzne
template typename T void swap(scoped_ptr T a, scoped_ptr T b);
Szablon funkcji realizującej preferowaną metodę wymiany zawartości dwóch obiektów
klasy scoped_ptr. To preferowana metoda podmiany, bo wywołanie swap(scoped1,
scoped2) może być stosowane w sposób uogólniony (w kodzie szablonowym) dla wielu
typów wskaźnikowych, w tym gołych wskaźników i inteligentnych wskaźników w imple-
mentacjach zewnętrznych2. Tymczasem alternatywne wywołanie scoped1.swap(scoped2)
zadziała tylko dla odpowiednio wyposażonych wskaźników inteligentnych, ale już nie
dla wskaźników zwykłych.
Stosowanie
Klasę scoped_ptr stosuje się jak zwykły typ wskaźnikowy, z paroma zaledwie (za to
istotnymi) różnicami; najważniejsza objawia się w tym, że nie trzeba pamiętać o wy-
woływaniu delete dla takiego wskaźnika i że nie można używać go w operacjach
kopiowania. Typowe operatory wyłuskania dla typów wskaźnikowych (operator*
i operator- ) są przeciążone dla klasy scoped_ptr, tak aby składniowo stosowanie
wskaźników inteligentnych nie różniło się od stosowania wskaźników zwykłych. Od-
wołania za pośrednictwem wskaźników scoped_ptr są równie szybkie, jak za pośred-
nictwem wskaźników zwykłych, nie ma tu też żadnych dodatkowych narzutów co do
rozmiaru — można więc ich używać powszechnie. Stosowanie klasy boost::scoped_ptr
wymaga włączenia do kodu pliku nagłówkowego boost/scoped_ptr.hpp . Przy dekla-
rowaniu wskaźnika scoped_ptr szablon konkretyzuje się typem obiektu wskazywanego.
Oto przykładowy scoped_ptr, kryjący wskaźnik obiektu klasy std::string:
boost::scoped_ptr std::string p(new std::string( Ahoj ));
Przy usuwaniu obiektu scoped_ptr jego destruktor sam wywołuje operator delete dla
przechowywanego wskaźnika.
Brak konieczności ręcznego usuwania obiektu
Spójrzmy na program, który wykorzystuje obiekt klasy scoped_ptr do zarządzania
wskaźnikiem obiektu klasy std::string. Zauważmy brak jawnego wywołania delete;
obiekt scoped_ptr jest zmienną automatyczną i jako taka podlega usuwaniu przy wy-
chodzeniu z zasięgu.
Dla takich zewnętrznych implementacji wskaźników inteligentnych, które nie udostępniają swojej
2
wersji swap, można napisać własną funkcję wymiany — przyp. aut.
42D:\roboczy jarek\makiety poprawki i druku pdf\Więcej niż C++ Wprowadzenie do bibliotek Boost\07 druk\r01-06.doc
Rozdział 1. ¨ Biblioteka Smart_ptr
43
#include boost/scoped_ptr.hpp
#include string
#include iostream
int main()
{
boost::scoped_ptr std::string
p(new std::string( Zawsze używaj scoped_ptr! ));
// Wypisanie ciągu na wyjściu programu
if (p)
std::cout *p \n ;
// Określenie długości ciągu
size_t i=p- size();
// Pzypisanie nowej wartości ciągu
*p= Jak zwykły wskaźnik ;
} // Tu usunięcie p i zwolnienie (delete) wskazywanego obiektu std::string
}
W powyższym kodzie wypadałoby zwrócić uwagę na kilka elementów. Przede wszyst-
kim scoped_ptr można testować jak zwykłe wskaźniki, bo udostępnia funkcję nie-
jawnej konwersji na typ zdatny do stosowania w wyrażeniach logicznych. Po drugie,
wywołanie metody na rzecz obiektu wskazywanego działa identycznie jak dla zwy-
kłych wskaźników, a to z racji obecności przeciążonego operatora operator- . Po
trzecie wreszcie, wyłuskanie scoped_ptr działa również tak, jak dla wskaźników zwy-
kłych — to za sprawą przeciążonego operatora operator*. Te własności czynią sto-
sowanie obiektów scoped_ptr (i innych wskaźników inteligentnych) tak naturalnym,
jak stosowanie najzwyklejszych gołych wskaźników. Różnice sprowadzają się więc
do wewnętrznego zarządzania czasem życia obiektu wskazywanego, nie do składni
odwołań.
Prawie jak auto_ptr
Zasadnicza różnica pomiędzy scoped_ptr a auto_ptr sprowadza się do traktowania
prawa własności do wskazywanego obiektu. Otóż auto_ptr przy kopiowaniu ochoczo
dokonuje transferu własności — poza źródłowy obiekt auto_ptr; tymczasem wskaź-
nika scoped_ptr po prostu nie można skopiować. Spójrzmy na poniższy program,
porównujący zachowanie auto_ptr i scoped_ptr w kontekście kopiowania.
void scoped_vs_auto() {
using boost::scoped_ptr;
using std::auto_ptr;
scoped_ptr std::string p_scoped(new std::string( Ahoj ));
auto_ptr std::string p_auto(new std::string( Ahoj ));
D:\roboczy jarek\makiety poprawki i druku pdf\Więcej niż C++ Wprowadzenie do bibliotek Boost\07 druk\r01-
06.doc
43
44
Część I ¨ Biblioteki ogólnego przeznaczenia
p_scoped- size();
p_auto- size();
scoped_ptr std::string p_another_scoped=p_scoped;
auto_ptr std::string p_another_auto=p_auto;
p_another_auto- size();
(*p_auto).size();
}
Niniejszy przykład nie da się nawet skompilować, bo scoped_ptr nie może uczestniczyć
w operacji konstrukcji kopiującej ani przypisania. Tymczasem auto_ptr da się i ko-
piować, i przypisywać kopiująco, przy czym te operacje realizują przeniesienie prawa
własności ze wskaźnika źródłowego (tu p_auto) do docelowego (tu p_another_auto),
zostawiając oryginał ze wskaźnikiem pustym. Może to prowadzić do nieprzyjemnych
niespodzianek, na przykład przy próbie umieszczenia obiektu auto_ptr w kontenerze3.
Gdyby z kodu usunąć przypisanie do p_another_scoped, program dałby się skompi-
lować, ale z kolei w czasie wykonania prowokowałby niewiadome zachowanie, a to
z powodu próby wyłuskania pustego wskaźnika p_auto (*p_auto).
Ponieważ metoda scoped_ptr::get zwraca goły wskaźnik, który może posłużyć do
rzeczy haniebnych, dlatego warto od razu zapamiętać dwie rzeczy, których trzeba
unikać. Po pierwsze, nie zwalniać samodzielnie wskaźnika przechowywanego w obiek-
cie scoped_ptr. Będzie on usuwany ponownie przy usuwaniu tegoż obiektu. Po drugie,
nie kopiować wyciągniętego wskaźnika do innego obiektu scoped_ptr (ani dowolnego
innego wskaźnika inteligentnego). Dwukrotne usunięcie wskaźnika, po razie przy usu-
waniu każdego z zawierających go obiektów scoped_ptr, może sprowokować nieszczę-
ście. Krótko mówiąc, get należy stosować wstrzemięźliwie i tylko tam, gdzie koniecz-
nie trzeba posługiwać się gołymi wskaźnikami!
Wskaźniki scoped_ptr a idiom prywatnej implementacji
Wskaźniki scoped_ptr świetnie nadają się do stosowania tam, gdzie wcześniej zasto-
sowania znajdowały wskaźniki zwykłe albo obiekty auto_ptr, a więc na przykład
w implementacjach idiomu prywatnej implementacji (ang. pimpl)4. Stojąca za nim
koncepcja sprowadza się do izolowania użytkowników klasy od wszelkich informacji
o prywatnych częściach tej klasy. Ponieważ użytkownicy klasy są uzależnieni od pli-
ku nagłówkowego tejże klasy, wszelkie zmiany w tym pliku nagłówkowym wymu-
szają ponowną kompilację kodu użytkowników, nawet jeśli zmiany ograniczały się do
obszarów prywatnych i zabezpieczonych klasy, a więc obszarów niby niedostępnych
z zewnątrz. Idiom implementacji prywatnej zakłada ukrywanie szczegółów prywatnych
3
Nigdy, przenigdy nie wolno umieszczać wskaźników auto_ptr w kontenerach biblioteki standardowej.
Próba taka sprowokuje zazwyczaj błąd kompilacji; jeśli nie, śmiałka czekają poważniejsze kłopoty
— przyp. aut.
4
Więcej o samym idiomie można przeczytać w książce Exceptional C++ (Wyjątkowy język C++
— przyp. tłum.) i pod adresem www.gotw.ca/gotw/024.htm — przyp. aut.
44D:\roboczy jarek\makiety poprawki i druku pdf\Więcej niż C++ Wprowadzenie do bibliotek Boost\07 druk\r01-06.doc
Rozdział 1. ¨ Biblioteka Smart_ptr
45
przez przeniesienie danych i metod prywatnych do osobnego typu definiowanego
w pliku implementacji; w pliku nagłówkowym mamy jedynie deklarację zapowiada-
jącą ów typ, a w wykorzystującej go klasie — wskaźnik tego typu. Konstruktor klasy
przydziela obiekt typu właściwego dla prywatnej implementacji, a destruktor klasy go
zwalnia. W ten sposób można usunąć niepożądane zależności z pliku nagłówkowego.
Spróbujmy skonstruować klasę implementującą idiom za pośrednictwem inteligent-
nych wskaźników.
// pimpl_sample.hpp
#if !defined (P#iP _dAiP e)
#define P#iP _dAiP e
struct impl;
class pimpl_sample {
impl* pimpl_;
public:
pimpl_sample();
~pimpl_sample();
void do_something();
};
#endif
Tak prezentuje się interfejs klasy pimpl_sample. Mamy tu też deklarację zapowiadają-
cą typu struct impl, reprezentującego typ prywatnej implementacji klasy i obejmują-
cego prywatne składowe i metody, definiowane w pliku implementacji klasy. W efek-
cie użytkownicy klasy pimpl_sample są zupełnie odizolowani od jej wewnętrznych
szczegółów implementacji.
// pimpl_sample.cpp
#include pimpl_sample.hpp
#include string
#include iostream
struct pimpl_sample::impl {
void do_something_() {
std::cout s_ \n ;
}
std::string s_;
};
pimpl_sample::pimpl_sample()
: pimpl_(new impl) {
pimpl_- s_ = Pimpl - idiom implementacji prywatnej ;
}
pimpl_sample::~pimpl_sample() {
D:\roboczy jarek\makiety poprawki i druku pdf\Więcej niż C++ Wprowadzenie do bibliotek Boost\07 druk\r01-
06.doc
45
46
Część I ¨ Biblioteki ogólnego przeznaczenia
delete pimpl_;
}
void pimpl_sample::do_something() {
pimpl_- do_something_();
}
Z pozoru kod wygląda zupełnie poprawnie, ale nie jest niestety doskonały. Otóż taka
implementacja nie jest odporna na wyjątki! Sęk w tym, że konstruktor pimpl_sample
może zrzucić wyjątek już po skonstruowaniu pimpl. Zgłoszenie wyjątku z konstruktora
oznacza, że konstruowany obiekt nigdy w pełni nie istniał, więc przy zwijaniu stosu
nie dochodzi do wywołania jego konstruktora. To zaś oznacza, że pamięć przydzielona
do wskaźnika pimpl_ nie zostanie zwolniona i dojdzie do wycieku. Można temu jednak
łatwo zaradzić: na ratunek przychodzi wskaźnik scoped_ptr!
class pimpl_sample {
struct impl;
boost::scoped_ptr impl pimpl;
…
};
Kwestię odporności na wyjątki załatwiamy, przekazując zadanie zarządzania czasem
życia obiektu ukrytej klasy impl na barki wskaźnika scoped_ptr i usuwając jawne
zwolnienie impl z destruktora klasy pimpl_sample (za sprawą scoped_ptr wywołanie
delete nie jest tam już potrzebne). Wciąż trzeba jednak pamiętać o konieczności de-
finiowania własnego destruktora; chodzi o to, że w czasie, kiedy kompilator genero-
wałby destruktor domyślny, typ impl nie byłby jeszcze znany w całości, więc nie na-
stąpiłoby wywołanie jego destruktora. Gdyby obiekt prywatnej implementacji był
wskazywany wskaźnikiem auto_ptr, kod taki skompilowałby się bez błędów; użycie
scoped_ptr prowokuje błąd kompilacji.
Kiedy scoped_ptr występuje w roli składowej klasy, trzeba ręcznie definiować dla tej
klasy konstruktor kopiujący i kopiujący operator przypisania. Chodzi naturalnie o to,
że wskaźników scoped_ptr nie można kopiować, więc klasa zawierająca takie wskaź-
niki również przestaje nadawać się do kopiowania (przynajmniej prostego kopiowania
składowych).
Na koniec warto zaznaczyć, że jeśli egzemplarz implementacji prywatnej (tu pimpl)
da się bezpiecznie dzielić pomiędzy egzemplarzami klasy z niej korzystającej (tu
pimpl_sample), wtedy do zarządzania czasem życia obiektu implementacji należałoby
wykorzystać wskaźnik klasy boost::shared_ptr. Zalety stosowania shared_ptr w takiej
sytuacji przejawiają się zwolnieniem z konieczności ręcznego definiowania konstrukto-
ra kopiującego i operatora przypisania dla klasy i pustego destruktora — shared_ptr
nadaje się do obsługi również typów niekompletnych.
46D:\roboczy jarek\makiety poprawki i druku pdf\Więcej niż C++ Wprowadzenie do bibliotek Boost\07 druk\r01-06.doc
Rozdział 1. ¨ Biblioteka Smart_ptr
47
scoped_ptr to nie to samo, co const auto_ptr
Uważny Czytelnik zorientował się już zapewne, że zachowanie wskaźnika auto_ptr
można by próbować upodobnić do zachowania scoped_ptr, deklarując ten pierwszy
ze słowem const:
const auto_ptr A no_transfer_of_ownership(new A);
To co prawda niezłe przybliżenie, ale niezupełne. Wciąż mamy taką różnicę, że wskaź-
nik scoped_ptr da się wyzerować (przestawić) wywołaniem metody reset, co pozwala
na podstawianie nowych obiektów wskazywanych w razie potrzeby. Nie da się tego
zrobić z użyciem const auto_ptr. Kolejna różnica, już nieco mniejsza, to różnica w wy-
mowie nazw: const auto_ptr znaczy mniej więcej tyle, co scoped_ptr, ale przy mniej
zwięzłym i jasnym zapisie. Zaś po przyswojeniu znaczenia scoped_ptr można stosować
go w celu jasnego wyrażania intencji programisty co do zachowania wskaźnika. Jeśli
trzeba gdzieś podkreślić osadzenie zasobu w zasięgu, przy równoczesnym wyrażeniu
niemożności przekazywania własności obiektu wskazywanego, należy tę chęć wyrazić
przez boost::scoped_ptr.
Podsumowanie
Gołe wskaźniki komplikują zabezpieczanie kodu przed wyjątkami i błędami wycieku
zasobów. Automatyzacja kontroli czasu życia obiektów przydzielanych dynamicznie
do danego zasięgu za pośrednictwem inteligentnych wskaźników to świetny sposób
wyeliminowania wad zwykłych wskaźników przy równoczesnym zwiększeniu czy-
telności, łatwości konserwacji i ogólnie pojętej jakości kodu. Stosowanie scoped_ptr
to jednoznaczne wyrażenie zamiaru blokowania współużytkowania i przenoszenia
prawa własności obiektu wskazywanego. Przekonaliśmy się, że std::auto_ptr może
podkradać wskazania innym obiektom tej klasy, co uważa się za największy grzech
auto_ptr. Właśnie dlatego scoped_ptr tak świetnie uzupełnia auto_ptr. Kiedy do
scoped_ptr przekazywany jest obiekt przydzielany dynamicznie, wskaźnik zakłada,
że posiada wyłączne prawo dysponowania obiektem. Ponieważ wskaźniki scoped_ptr
są niemal zawsze przydzielane jako obiekty (zmienne bądź składowe) automatyczne,
mamy gwarancję ich usunięcia przy wychodzeniu z zasięgu, co prowadzi do pewnego
zwolnienia obiektów wskazywanych, niezależnie od tego, czy opuszczenie zasięgu
było planowe (instrukcją return), czy awaryjne, wymuszone zgłoszeniem wyjątku.
Wskaźniki scoped_ptr należy stosować tam, gdzie:
t
w zasięgu obarczonym ryzykiem zgłoszenia wyjątku występuje wskaźnik;
t
funkcja ma kilka ścieżek wykonania i kilka punktów powrotu;
t
czas życia obiektu przydzielanego dynamicznie ma być ograniczony
do pewnego zasięgu;
t
ważna jest odporność na wyjątki (czyli prawie zawsze!).
D:\roboczy jarek\makiety poprawki i druku pdf\Więcej niż C++ Wprowadzenie do bibliotek Boost\07 druk\r01-
06.doc
47
48
Część I ¨ Biblioteki ogólnego przeznaczenia
scoped_array
Nagłówek: boost/scoped_array.hpp
Potrzebę tworzenia dynamicznie przydzielanych tablic elementów zwykło się zaspo-
kajać za pomocą kontenera std::vector, ale w co najmniej dwóch przypadkach uza-
sadnione jest użycie „zwykłych” tablic: przy optymalizowaniu kodu (bo implementacja
kontenera vector może wprowadzać narzuty czasowe i pamięciowe) i przy wyrażaniu
jasnej intencji co do sztywnego rozmiaru tablicy5. Tablice przydzielane dynamicznie
prowokują zagrożenia właściwe dla zwykłych wskaźników, z dodatkowym (zbyt czę-
stym) ryzykiem omyłkowego zwolnienia tablicy wywołaniem delete zamiast delete [].
Omyłki te zdarzyło mi się widywać w najmniej oczekiwanych miejscach, nawet
w powszechnie stosowanych, komercyjnych implementacjach klas kontenerów! Klasa
scoped_array jest dla tablic tym, czym scoped_ptr dla wskaźników: przejmuje odpo-
wiedzialność za zwolnienie pamięci tablicy. Tyle że scoped_array robi to za pomocą
operatora delete [].
Przyczyna, dla której scoped_array jest osobną klasą, a nie na przykład specjalizacją
scoped_ptr, tkwi w niemożności rozróżnienia wskaźników pojedynczych elementów
i wskaźników całych tablic za pomocą technik metaprogramowania. Mimo wysiłków
nikt nie znalazł jeszcze pewnego sposobu różnicowania tych wskaźników; sęk w tym,
że wskaźniki tablic dają się tak łatwo przekształcać we wskaźniki pojedynczych obiek-
tów i nie zachowują w swoim typie żadnych informacji o tym, że wskazywały właśnie
tablice. W efekcie trzeba zastosowania wskaźników zwykłych i wskaźników tablic
różnicować samodzielnie, stosując dla nich jawnie scoped_ptr i scoped_array — tak
jak samodzielnie trzeba pamiętać o zwalnianiu wskazywanych tablic wywołaniem
delete [] zamiast delete. Uciekając się do stosowania scoped_array, zyskujemy au-
tomatyzm zwalniania tablicy i jasność intencji: wiadomo od razu, że chodzi o wskaź-
nik tablicy, a nie pojedynczego elementu.
Wskaźnik scoped_array przypomina bardzo scoped_ptr; jedną z niewielu różnic jest
przeciążanie (w tym pierwszym) operatora indeksowania operator[], umożliwiającego
stosowanie naturalnej składni odwołań do elementów tablicy.
Wskaźnik scoped_array należy uznać za pod każdym względem lepszą alternatywę
dla klasycznych tablic przydzielanych dynamicznie. Przejmuje on zarządzanie czasem
życia tablic tak, jak scoped_ptr przejmuje zarządzenie czasem życia obiektów wska-
zywanych. Trzeba jednak pamiętać, że w bardzo wielu przypadkach jest alternatywą
gorszą niż kontener std::vector (elastyczniejszy i dający większe możliwości). Wskaź-
nik scoped_array zdaje się przewyższać kontenery std::vector jedynie tam, gdzie trzeba
jasno wyrazić chęć sztywnego ograniczenia rozmiaru tablicy.
5
W rzeczy samej, w zdecydowanej większości przypadków lepiej faktycznie skorzystać ze standardowej
implementacji kontenera vector. Decyzja o stosowaniu scoped_array powinna wynikać z pomiarów
wydajności — przyp. aut.
48D:\roboczy jarek\makiety poprawki i druku pdf\Więcej niż C++ Wprowadzenie do bibliotek Boost\07 druk\r01-06.doc
Rozdział 1. ¨ Biblioteka Smart_ptr
49
shared_ptr
Nagłówek: boost/shared_ptr.hpp
Chyba każdy nietrywialny program wymaga stosowania jakiejś postaci inteligentnych
wskaźników ze zliczaniem odwołań do obiektów wskazywanych. Takie wskaźniki
eliminują konieczność kodowania skomplikowanej logiki sterującej czasem życia obiek-
tów współużytkowanych przez pewną liczbę innych obiektów. Kiedy wartość licznika
odwołań spadnie do zera, oznacza to brak obiektów zainteresowanych użytkowaniem
danego zasobu i możliwość jego zwolnienia. Inteligentne wskaźniki ze zliczaniem
odwołań można podzielić na ingerencyjne i nieingerencyjne (ang. odpowiednio: in-
trusive i non-intrusive). Te pierwsze wymagają od klas obiektów zarządzanych udo-
stępniania specjalnych metod albo składowych, za pomocą których realizowane jest
zliczanie odwołań. Oznacza to konieczność projektowania klas zasobów współużyt-
kowanych z uwzględnieniem wymaganej infrastruktury albo późniejszego uzdatniania
takich klas przez np. pakowanie ich w klasy implementujące infrastrukturę zliczania
odwołań. Z kolei wskaźniki zliczające odwołania w sposób nieingerencyjny nie wy-
magają niczego od typu obiektu zarządzanego. Wskaźniki zliczające odwołania za-
kładają wyłączność własności pamięci skojarzonej z przechowywanymi wskaźnikami.
Kłopot ze współużytkowaniem obiektu bez pomocy ze strony inteligentnych wskaź-
ników polega na tym, że choć trzeba wreszcie zwolnić taki obiekt, nie wiadomo,
kto i kiedy miałby to zrobić. Bez pomocy ze strony mechanizmu zliczania odwołań
trzeba arbitralnie i zewnętrznie ograniczyć czas życia obiektu współużytkowanego,
co oznacza zwykle związanie jego użytkowników nadmiernie silnymi zależnościami.
To z kolei niekorzystnie wpływa na prostotę kodu i jego zdatność do wielokrotnego
użycia.
Klasa obiektu zarządzanego może przejawiać własności predestynujące ją do stoso-
wania ze wskaźnikami zliczającymi odwołania. Takimi cechami mogą być kosztowność
operacji kopiowania albo współużytkowanie części implementacji pomiędzy wieloma
egzemplarzami klasy. Są też sytuacje, w których nie istnieje jawny właściciel współ-
użytkowanego zasobu. Stosowanie inteligentnych wskaźników ze zliczaniem odwo-
łań umożliwia dzielenie własności pomiędzy obiektami, które wymagają dostępu do
wspólnego zasobu. Wskaźniki zliczające odwołania umożliwiają również przecho-
wywanie wskaźników obiektów w kontenerach biblioteki standardowej bez ryzyka
wycieków pamięci, zwłaszcza w obliczu wyjątków albo operacji usuwania elementów
z kontenera. Z kolei przechowywanie wskaźników w kontenerach pozwala na wyko-
rzystanie zalet polimorfizmu, zwiększenie efektywności składowania (w przypadku klas
zakładających kosztowne kopiowanie) i możliwość składowania tych samych obiektów
w wielu specjalizowanych kontenerach, wybranych ze względu na np. efektywność
sortowania.
Po stwierdzeniu chęci zastosowania inteligentnego wskaźnika zliczającego odwołania
trzeba zdecydować między implementacją ingerencyjną i nieingerencyjną. Niemal
zawsze lepszym wyborem są wskaźniki nieingerencyjne, a to z racji ich uniwersalno-
ści, braku wpływu na istniejący kod i elastyczności. Wskaźniki nieingerencyjne moż-
na stosować z klasami, których z pewnych względów nie można zmieniać. Zwykle
klasę adaptuje się do współpracy z inteligentnym wskaźnikiem ingerencyjnym przez
D:\roboczy jarek\makiety poprawki i druku pdf\Więcej niż C++ Wprowadzenie do bibliotek Boost\07 druk\r01-
06.doc
49
50
Część I ¨ Biblioteki ogólnego przeznaczenia
wyprowadzenie klasy pochodnej z klasy bazowej zliczającej odwołania. Taka mody-
fikacja klasy może być kosztowniejsza, niż się zdaje. W najlepszym przypadku trzeba
ponieść koszt w postaci zacieśnienia zależności i zmniejszenia zdatności klasy do
użycia w innych kontekstach6. Zwykle oznacza to również zwiększenie rozmiaru
obiektów klasy adaptowanej, co w niektórych kontekstach ogranicza jej przydatność7.
Obiekt klasy shared_ptr można skonstruować na bazie zwykłego wskaźnika, innego
obiektu shared_ptr i obiektów klasy std::auto_ptr bądź boost:weak_ptr. Do kon-
struktora shared_ptr można też przekazać drugi argument, w postaci dealokatora
(ang. deleter). Jest on później wykorzystywany do obsługi operacji usunięcia zasobu
współużytkowanego. Przydaje się w zarządzaniu takimi zasobami, które nie były przy-
dzielane wywołaniem new i nie powinny być zwalniane zwykłym delete (przykłady
tworzenia własnych dealokatorów zostaną zaprezentowane dalej). Po skonstruowaniu
obiektu shared_ptr można go stosować jak najzwyklejszy wskaźnik, z tym wyjąt-
kiem, że nie trzeba jawnie zwalniać obiektu wskazywanego.
Poniżej prezentowana jest częściowa deklaracja szablonu shared_ptr; występują tu
najważniejsze składowe i metody, które za chwilę doczekają się krótkiego omówienia.
namespace boost {
template typename T class shared_ptr {
public:
template class Y explicit shared_ptr(Y* p);
template class Y, class D shared_ptr(Y* p, D d);
~shared_ptr();
shared_ptr(const shared_ptr r);
template class Y explicit shared_ptr(const weak_ptr Y r);
template classY explicit shared_ptr(std::auto_ptr Y r);
shared_ptr operator=(const shared_ptr r);
void reset();
T operator*() const;
T* operator- () const;
T* get() const;
bool unique() const;
long use_count() const;
operator nieokreślony-typ-logiczny() const;
6
Weźmy choćby przypadek, kiedy jedną klasę obiektu zarządzanego trzeba by przystosować do stosowania
z kilkoma klasami ingerencyjnych, inteligentnych wskaźników zliczających odwołania. Owe różne klasy
bazowe infrastruktury zliczania odwołań mogłyby być niezgodne ze sobą; gdyby zaś tylko jedna z klas
wskaźników była kategorii ingerencyjnej, stosowanie wskaźników nieingerencyjnych odbywałoby się
ze zbędnym wtedy narzutem jednej klasy bazowej — przyp. aut.
7
Z drugiej strony nieingerencyjne wskaźniki inteligentne wymagają dla siebie przydziału dodatkowej
pamięci dla niezbędnej infrastruktury zliczania odwołań — przyp. aut.
50D:\roboczy jarek\makiety poprawki i druku pdf\Więcej niż C++ Wprowadzenie do bibliotek Boost\07 druk\r01-06.doc
Rozdział 1. ¨ Biblioteka Smart_ptr
51
void swap(shared_ptr T b);
};
template class T, class
shared_ptr T static_pointer_cast(const shared_ptr r);
}
Metody
template class Y explicit shared_ptr(Y* p);
Konstruktor przejmujący w posiadanie przekazany wskaźnik p. Argument wywoła-
nia powinien być poprawnym wskaźnikiem Y. Konstrukcja powoduje ustawienie licz-
nika odwołań na 1. Jedynym wyjątkiem, jaki może być zrzucony z konstruktora, jest
std::bad_alloc (w mało prawdopodobnym przypadku, kiedy nie ma pamięci do
przydzielenia licznika odwołań).
template class Y, class D shared_ptr(Y* p, D d);
Konstruktor przyjmujący parę argumentów. Pierwszy z nich to zasób, który ma przejść
pod opiekę tworzonego wskaźnika shared_ptr, drugi to obiekt odpowiedzialny za
zwolnienie zasobu wskazywanego przy usuwaniu wskaźnika. Zasób jest przekazywa-
ny do obiektu zwalniającego wywołaniem d(p). Z tego względu poprawne wartości p
zależą od d. Jeśli nie uda się przydzielić licznika odwołań, konstruktor zrzuci wyjątek
std::bad_alloc.
shared_ptr(const shared_ptr r);
Zasób wskazywany przez r jest współdzielony z nowo tworzonym wskaźnikiem sha-
red_ptr, co powoduje zwiększenie licznika odwołań o 1. Konstruktor kopiujący nie
zrzuca wyjątków.
template class Y explicit shared_ptr(const weak_ptr Y r);
Konstruuje wskaźnik shared_ptr na podstawie wskaźnika weak_ptr (omawianego
w dalszej części rozdziału). Pozwala to na zabezpieczenie zastosowania weak_ptr
w aplikacjach wielowątkowych przez zwiększenie licznika odwołań do zasobu współ-
użytkowanego wskazywanego przez argument typu weak_ptr (wskaźniki weak_ptr nie
wpływają na stan liczników odwołań do zasobów współdzielonych). Jeśli wskaźnik
weak_ptr jest pusty (to jest r.use_count() == 0), konstruktor shared_ptr zrzuca wy-
jątek typu bad_weak_ptr.
template classY explicit shared_ptr(std::auto_ptr Y r);
Konstrukcja wskaźnika shared_ptr na bazie auto_ptr oznacza przejęcie własności
wskaźnika przechowywanego w r przez utworzenie jego kopii i wywołanie na rzecz
źródłowego obiektu auto_ptr metody release. Licznik odwołań po konstrukcji ma
wartość 1. Oczywiście r jest zerowane. Niemożność przydziału pamięci dla licznika
odwołań prowokuje wyjątek std::bad_alloc.
D:\roboczy jarek\makiety poprawki i druku pdf\Więcej niż C++ Wprowadzenie do bibliotek Boost\07 druk\r01-
06.doc
51
52
~shared_ptr();
Część I ¨ Biblioteki ogólnego przeznaczenia
Destruktor klasy shared_ptr zmniejsza o 1 licznik odwołań do zasobu wskazywanego.
Jeśli wartość licznika spadnie w wyniku zmniejszenia do zera, destruktor zwolni obiekt
wskazywany. Polega to na wywołaniu dlań operatora delete albo przekazanego w kon-
struktorze obiektu zwalniającego; w tym ostatnim przypadku jedynym argumentem
wywołania obiektu zwalniającego jest wskaźnik przechowywany w shared_ptr. De-
struktor nie zrzuca wyjątków.
shared_ptr operator=(const shared_ptr r);
Operator przypisania inicjuje współużytkowanie zasobu z r, z zaniechaniem współ-
użytkowania zasobu bieżącego. Nie zrzuca wyjątków.
void reset();
Metoda reset służy do rezygnacji ze współdzielenia zasobu wskazywanego przecho-
wywanym wskaźnikiem; wywołanie zmniejsza licznik odwołań do zasobu.
T operator*() const;
Przeciążony operator zwracający referencję obiektu (zasobu) wskazywanego. Zacho-
wanie operatora dla pustego wskaźnika przechowywanego jest niezdefiniowane. Ope-
rator nie zrzuca wyjątków.
T* operator- () const;
Przeciążony operator zwracający przechowywany wskaźnik. Razem z przeciążonym
operatorem wyłuskania operator* upodobnia zachowanie shared_ptr do zwykłych
wskaźników. Operator nie zrzuca wyjątków.
T* get() const;
Metoda get to zalecany sposób odwoływania się do wskaźnika przechowywanego
w obiekcie shared_ptr, kiedy istnieje podejrzenie, że wskaźnik ten ma wartość pustą
(kiedy to wywołania operatorów operator* i operator- prowokują niezdefiniowane
zachowanie). Zauważmy, że stan wskaźnika w wyrażeniach logicznych można testować
również za pośrednictwem funkcji niejawnej konwersji shared_ptr na typ logiczny.
Metoda nie zrzuca wyjątków.
bool unique() const;
Metoda zwraca true, jeśli obiekt shared_ptr, na rzecz którego nastąpiło wywołanie,
jest jedynym właścicielem przechowywanego wskaźnika. W pozostałych przypadkach
zwraca false. Metoda nie zrzuca wyjątków.
long use_count() const;
Metoda use_count zwraca wartość licznika odwołań do wskaźnika przechowywanego
w obiekcie shared_ptr. Przydaje się w diagnostyce, bo pozwala na zdejmowanie migawek
wartości licznika odwołań w krytycznych punktach wykonania programu. Stosować
52D:\roboczy jarek\makiety poprawki i druku pdf\Więcej niż C++ Wprowadzenie do bibliotek Boost\07 druk\r01-06.doc
Rozdział 1. ¨ Biblioteka Smart_ptr
53
wstrzemięźliwie; dla niektórych możliwych implementacji interfejsu shared_ptr ustale-
nie liczby odwołań może być kosztowne obliczeniowo albo nawet niemożliwe. Metoda
nie zrzuca wyjątków.
operator nieokreślony-typ-logiczny() const;
Niejawna konwersja na typ logiczny, umożliwiająca stosowanie obiektów shared_ptr
w wyrażeniach logicznych (i tam, gdzie oczekiwane są wyrażenia logiczne). Zwracana
wartość to true, jeśli shared_ptr przechowuje obecnie jakiś ustawiony wskaźnik, bądź
false w pozostałych przypadkach. Zauważmy, że typ wartości zwracanej nie jest okre-
ślony; zastosowanie typu bool pozwalałoby na angażowanie wskaźników shared_ptr
w niektórych bezsensownych dla jego semantyki operacjach, stąd w implementacji
najczęściej stosowany jest idiom bezpiecznej wartości logicznej8, który ogranicza za-
stosowania wartości zwracanej do odpowiednich testów logicznych. Metoda nie zrzuca
wyjątków.
void swap(shared_ptr T b);
Niekiedy trzeba wymienić wskaźniki pomiędzy dwoma obiektami shared_ptr. Metoda
swap wymienia przechowywane wskaźniki oraz liczniki odwołań. Nie zrzuca wyjątków.
Funkcje zewnętrzne
template class T, class
shared_ptr T static_pointer_cast(const shared_ptr r);
Wykonanie statycznego rzutowania wskaźnika przechowywanego w shared_ptr można
zrealizować poprzez wydobycie wskaźnika i wywołanie static_cast, ale wyniku nie
można by skutecznie umieścić w innym wskaźniku shared_ptr: nowy wskaźnik sha-
red_ptr uznałby, że jest pierwszym posiadaczem zasobu wskazywanego. Problem
eliminuje funkcja static_pointer_cast. Gwarantuje ona zachowanie poprawnej wartości
licznika odwołań. Funkcja nie zrzuca wyjątków.
Stosowanie
Zasadniczym celem stosowania shared_ptr jest eliminowanie konieczności kodo-
wania logiki wykrywającej właściwy moment zwolnienia zasobu współużytkowa-
nego przez wielu użytkowników. Poniżej mamy prosty przykład, gdzie dwie klasy —
A i B — korzystają ze wspólnego egzemplarza wartości typu int. Uwaga: korzysta-
nie ze wskaźników shared_ptr wymaga włączenia do kodu pliku nagłówkowego
boost/shared_ptr.hpp .
#include boost/shared_ptr.hpp
#include cassert
class A {
Autorstwa Petera Dimowa — przyp. aut.
8
D:\roboczy jarek\makiety poprawki i druku pdf\Więcej niż C++ Wprowadzenie do bibliotek Boost\07 druk\r01-
06.doc
53
54
Część I ¨ Biblioteki ogólnego przeznaczenia
boost::shared_ptr int no_;
public:
A(boost::shared_ptr int no) : no_(no) {}
void value(int i) {
*no_ = i;
}
};
class B {
boost::shared_ptr int no_;
public:
B(boost::shared_ptr int no) : no_(no) {}
void value() const {
return *no_;
}
};
int main() {
boost::shared_ptr int temp(new int(14));
A a(temp);
B b(temp);
a.value(28);
assert(b.value()==28);
}
Klasy A i B z powyższego przykładu zawierają składową typu shared_ptr int . Przy
tworzeniu egzemplarzy A i B przekazujemy do obu konstruktorów ten sam egzemplarz
obiektu shared_ptr: temp. Oznacza to, że mamy trzy wskaźniki odnoszące się do tej
samej zmiennej typu int: jeden w postaci obiektu temp i dwa zaszyte w składowych
no_ klas A i B. Gdyby współużytkowanie zmiennej było realizowane za pomocą zwy-
kłych wskaźników, klasy A i B miałyby ciężki orzech do zgryzienia w postaci wyty-
powania właściwego momentu zwolnienia zmiennej. W tym przykładzie licznik od-
wołań do zmiennej ma aż do końca funkcji main wartość 3; u kresu zasięgu funkcji
wszystkie obiekty shared_ptr zostaną usunięte, co będzie powodować zmniejszanie
licznika aż do zera. Wyzerowanie sprowokuje zaś usunięcie przydzielonej zmiennej
typu int.
Jeszcze o idiomie implementacji prywatnej
Idiom prywatnej implementacji rozpatrywaliśmy wcześniej w aspekcie wskaźników
scoped_ptr, znakomicie nadających się do przechowywania dynamicznie przydziela-
nych egzemplarzy implementacji prywatnej tam, gdzie wcielenie idiomu nie zakłada
kopiowania i współdzielenia implementacji pomiędzy egzemplarzami. Nie wszystkie
klasy, w których można by zastosować prywatną implementację, spełniają te warunki
(co nie znaczy, że w ogóle nie można zastosować w nich wskaźników scoped_ptr,
wymaga to jednak ręcznej implementacji operacji kopiowania i przypisywania im-
plementacji). Dla takich klas, które zakładają dzielenie szczegółów implementacji
pomiędzy wieloma egzemplarzami, należałoby zarządzać wspólnymi implementacjami
za pomocą wskaźników shared_ptr. Kiedy wskaźnikowi shared_ptr przekazany zosta-
nie w posiadanie egzemplarz implementacji prywatnej, zyskamy „za darmo” możliwość
54D:\roboczy jarek\makiety poprawki i druku pdf\Więcej niż C++ Wprowadzenie do bibliotek Boost\07 druk\r01-06.doc
Rozdział 1. ¨ Biblioteka Smart_ptr
55
kopiowania i przypisywania implementacji. W przypadku scoped_ptr kopiowanie i przy-
pisywanie było niedozwolone, bo semantyka wskaźników scoped_ptr nie dopuszcza
kopiowania. Z tego względu obsługa kopiowania i przypisań w klasach stosujących
scoped_ptr i utrzymujących prywatne implementacje wspólne wymagałaby ręcznego
definiowania operatora przypisania i konstruktora kopiującego. Kiedy w roli wskaź-
nika prywatnej implementacji klasy występuje shared_ptr, nie trzeba udostępniać
własnego konstruktora kopiującego. Egzemplarz implementacji będzie współużytko-
wany przez obiekty klasy; jedynie w przypadku, kiedy któryś z egzemplarzy klasy ma
być wyróżniony osobnym stanem, trzeba będzie zdefiniować stosowny konstruktor
kopiujący. Sama obsługa implementacji prywatnej nie różni się poza tym od przypadku,
kiedy wskazywał ją wskaźnik scoped_ptr: wystarczy zamienić w kodzie scoped_ptr
na shared_ptr.
shared_ptr a kontenery biblioteki standardowej języka C++
Przechowywanie obiektów wprost w kontenerze jest niekiedy kłopotliwe. Przecho-
wywanie przez wartość oznacza, że użytkownicy kontenera wydobywający zeń obiekty
otrzymują ich kopie, co w przypadku obiektów cechujących się wysokim kosztem
kopiowania może znacząco wpływać na wydajność programu. Co więcej, niektóre
kontenery, zwłaszcza std::vector, podejmują operację kopiowania przechowywa-
nych elementów w obliczu konieczności zmiany rozmiaru kontenera, co znów stanowi
dodatkowy, potencjalnie ważki koszt. Wreszcie semantyka typowa dla wartości ozna-
cza brak zachowania polimorficznego. Jeśli obiekty przechowywane w kontenerach
mają przejawiać zachowanie polimorficzne, należałoby skorzystać ze wskaźników.
Jeśli będą to wskaźniki zwykłe, staniemy w obliczu poważnego problemu zarządzania
spójnością wskazań w poszczególnych elementach kontenera. Choćby przy usuwaniu
elementów z kontenera trzeba będzie sprawdzić, czy istnieją jeszcze użytkownicy ko-
rzystający z kopii wskaźników wydobytych wcześniej z kontenera; trzeba też będzie
koordynować odwołania do tego samego elementu inicjowane przez różnych użyt-
kowników. Wszystkie te złożone zadania może z powodzeniem przejąć shared_ptr.
Poniższy przykład ilustruje sposób przechowywania współdzielonych wskaźników
w kontenerze biblioteki standardowej.
#include boost/shared_ptr.hpp
#include vector
#include iostream
class A {
public:
virtual void sing()=0;
protected:
virtual ~A() {};
};
class B : public A {
public:
virtual void sing() {
std::cout do re mi fa sol la ;
}
D:\roboczy jarek\makiety poprawki i druku pdf\Więcej niż C++ Wprowadzenie do bibliotek Boost\07 druk\r01-
06.doc
55
Część I ¨ Biblioteki ogólnego przeznaczenia
56
};
boost::shared_ptr A createA() {
boost::shared_ptr A p(new B());
return p;
}
int main() {
typedef std::vector boost::shared_ptr A container_type;
typedef container_type::iterator iterator;
container_type container;
for (int i=0;i 10;++i) {
container.push_back(createA());
}
std::cout Pr ba ch ru: \n ;
iterator end=container.end();
for (iterator it=container.begin();it!=end;++it) {
(*it)- sing();
}
}
Dwie widniejące powyżej klas
Pobierz darmowy fragment (pdf)