2009.11_Sprytne wskaźniki _[Programowanie C C++].pdf

(498 KB) Pobierz
441721404 UNPDF
Programowanie C++
Sprytne wskaźniki
Automatyczne niszczenie obiektów utworzonych na stercie
w C++
Programista używając C++ musi dbać o zwalnianie obiektów
dynamicznych (utworzonych na stercie). Zadanie to można
automatyzować, wykorzystując obiekty pośredniczące, tak zwane
sprytne wskaźniki. Narzuty czasowe i pamięciowe tego rozwiązania
są pomijalne w większości zastosowań.
Dowiesz się:
• Jak zarządzać obiektami utworzonymi na
stercie;
• Co to są sprytne wskaźniki;
• Jak automatycznie zwalniać obiekty, gdy
występują cykliczne zależności.
Powinieneś wiedzieć:
• Jak pisać proste programy w C++;
• Co to są obiekty dynamiczne (utworzone na
stercie).
sie kompilacji, tworząc odpowiednie klasy, nie
dodając żadnych narzutów pamięciowych i cza-
sowych (kod generowany nie różni się od kodu
tworzonego ręcznie ), jedyną niedogodnością jest
dłuższy czas kompilacji.
Sprytnym wskaźnikiem jest klasa boost::
scoped_ptr przedstawiona na Listingu 1. Kon-
struktor kopiujący oraz operator przypisania
jest zabroniony (prywatny), ponieważ utwo-
rzenie więcej niż jednego wskaźnika tego ty-
pu przechowującego ten sam obiekt prowadzi
do błędu, polegającego na próbie powtórnego
zwolnienia obiektu dynamicznego.
Sprytny wskaźnik, oprócz usuwana obiek-
tów po wyjściu z bloku, ułatwia zarządzanie
obiektami dynamicznymi, które są składowy-
mi klasy, ponieważ nie trzeba ich jawnie zwal-
niać w destruktorze (patrz Listing 2). Dodat-
kową zaletą takiego rozwiązania jest poprawne
zwalnianie zasobów, gdy są zgłaszane wyjątki.
Jeżeli konstruktor klasy Audio (Listing 2) zgło-
si wyjątek, to sprytny wskaźnik image_ sprawi,
że obiekt Image zostanie skasowany.
Poziom
trudności
sprytny wskaźnik usuwa zarządzany obiekt
w destruktorze, technika ta jest nazywana
zdobywanie zasobów jest inicjowaniem . Jeże-
li obiekt jest wskazywany przez wiele spryt-
nych wskaźników, to powinien być usunięty
w destruktorze ostatniego z nich.
mocą operatora new ) powinny być
zwalniane przez programistę wte-
dy, kiedy nie są już używane (operator delete ).
Brak restrykcyjnej kontroli dotyczącej czasu ży-
cia tych obiektów powoduje wycieki pamię-
ci, co oznacza, że aplikacja zajmuje coraz wię-
cej pamięci operacyjnej, kończąc działanie błę-
dem, kiedy pamięć ta zostanie wyczerpana. Po-
niższy tekst pokazuje wykorzystanie mechani-
zmów związanych z wołaniem konstruktorów
i destruktorów do automatycznego zwalniania
obiektów tworzonych na stercie, co zwalnia pro-
gramistę z tego zadania.
Dostęp do obiektów utworzonych na ster-
cie jest realizowany przez wskaźnik, jeżeli
taki obiekt nie jest wskazywany, to nie mo-
że być używany, więc można go zwolnić.
Sprytne wskaźniki pośredniczą przy dostę-
pie do obiektów dynamicznych (wskaźnik
jest przekazywany w konstruktorze) bada-
jąc, czy obiekt jest wskazywany i jeżeli nie,
usuwając go. Zakładając, że obiekt dyna-
miczny może być wskazywany tylko przez je-
den sprytny wskaźnik, to obiekt ten możemy
usunąć w momencie niszczenia sprytnego
wskaźnika. W tym najprostszym przypadku
Sprytne wskaźniki będące wyłącz-
nymi posiadaczami obiektów
Biblioteka standardowa C++ oraz biblioteki
boost dostarczają różnych rodzajów sprytnych
wskaźników. Są to szablony, ponieważ potrze-
bujemy wskaźników zarządzających różnymi
typami obiektów. Szablony generują kod w cza-
���������������� �������
���������������� �������
������
������
Rysunek 1. Sprytne wskaźniki ze zliczaniem odniesień współdzielą wskaźnik do obiektu oraz licznik.
Obiekt sprytnego wskaźnika może zawierać jeden lub dwa wskaźniki
Szybki start
Aby uruchomić przedstawione przykłady, należy mieć dostęp do dowolnego kompilatora
C++ oraz edytora tekstu. Większość przykładów korzysta z udogodnień dostarczanych przez
biblioteki boost, warunkiem ich uruchomienia jest instalacja tych bibliotek (w wersji 1.36 lub
nowszej) oraz wykorzystywanie kompilatora oicjalnie przez nie wspieranego, którymi są:
msvc 7.1 lub nowszy, gcc g++ 3.4 lub nowszy, Intell C++ 8.1 lub nowszy, Sun Studio 12 lub
Darvin/GNU C++ 4.x. Na wydrukach pominięto dołączanie odpowiednich nagłówków oraz
udostępnianie przestrzeni nazw, pełne źródła dołączono jako materiały pomocnicze.
40
11/2009
O biekty utworzone na stercie (za po-
441721404.051.png 441721404.052.png 441721404.053.png 441721404.054.png 441721404.001.png 441721404.002.png 441721404.003.png 441721404.004.png 441721404.005.png 441721404.006.png 441721404.007.png 441721404.008.png 441721404.009.png 441721404.010.png 441721404.011.png 441721404.012.png
Sprytne wskaźniki
Biblioteka standardowa C++ zawiera sza-
blon std::auto_ptr , który jest innym ty-
pem sprytnego wskaźnika będącego wyłącz-
nym posiadaczem obiektu. Ma on nietypo-
wą implementację konstruktora kopiującego
i operatora przypisania, operacje te są przeka-
zywaniem własności, a nie tworzeniem kopii
(patrz Listing 3).
Wskaźnik ten możemy wykorzystać
(oprócz automatycznego kasowania obiek-
tu przy usuwaniu wskaźnika) do zwracania
obiektu utworzonego na stercie, unikając
wycieków pamięci. Listing 4 zawiera funk-
cję createFoo , która ilustruje tę technikę. Je-
żeli będziemy ignorowali wartość zwracaną,
to destruktor obiektu tymczasowego zwolni
obiekt, jeżeli wartość zwracana zostanie przy-
pisana do innego obiektu, to obiekt tymcza-
sowy będzie pusty.
Nietypowa implementacja konstruktora
kopiującego i operatora przypisania (przeka-
zywanie własności, a nie tworzenie kopii) za-
pobiega tworzeniu więcej niż jednego wskaź-
nika auto_ptr zarządzającego tym samym
obiektem. Brak możliwości tworzenia kopii
ogranicza możliwość stosowania tych wskaź-
ników, na przykład obiekty auto_ptr nie
powinny być przechowywane w kolekcjach
standardowych, ponieważ one zawsze prze-
chowują kopię.
Listing 1. Wybrane metody szablonu scoped_ptr
template < typename T > class scoped_ptr {
public :
explicit scoped_ptr ( T * p = 0 ) : p_ ( p ) {}
~ scoped_ptr (){ delete p_ ; } //usuwa wskazywany obiekt
T & operator * () { return * p_ ; } //dostęp do zarządzanego obiektu
T * operator -> () { return p_ ; } //dostęp do zarządzanego obiektu
//także inne metody, np. porównywanie wskaźników
private :
T * p_ ; //Wskaźnik, którym zarządza
scoped_ptr ( scoped_ptr const & ); //zabroniony konstruktor kopiujący
scoped_ptr & operator = ( scoped_ptr const & ); //zabronione przypisanie
};
Listing 2. Składowe przechowywane jako sprytne wskaźniki upraszczają kod
class Book { //przykładowa klasa książki, która agreguje obrazek oraz nagranie
public :
Book ( const string & name , const string & image_name , const string & audio_name )
: name_ ( name ),
image_ ( new Image ( image_name ) ),
audio_ ( new Audio ( audio_name ) )
{}
~ Book () {} //destruktor może być pusty
private :
string name_ ;
scoped_ptr < Image > image_ ;
scoped_ptr < Audio > audio_ ;
};
Sprytne wskaźniki
ze zliczaniem odniesień
Przedstawione poprzednio sprytne wskaźni-
ki nie pozwalają na współdzielenie tego same-
go obiektu przez kilka wskaźników. Wady tej
nie mają sprytne wskaźniki ze zliczaniem od-
niesień pokazane na Rysunku 1. Obiekty te
zajmują więcej pamięci niż zwykłe wskaźni-
ki, ponieważ wymagają one licznika odniesień.
Sprytne wskaźniki, przejmując nowy obiekt
utworzony na stercie, tworzą licznik i inicjują
go wartością 1, w konstruktorze kopiującym
licznik ten jest zwiększany, w destruktorze
zmniejszany, jeżeli osiągnie wartość 0, to licz-
nik oraz zarządzany obiekt jest usuwany.
Wskaźnikiem ze zliczaniem odniesień jest
szablon boost::shared_ptr (wchodzi on w
skład nowego standardu C++200x). Takie
sprytne wskaźniki pozwalają wygodnie mani-
pulować obiektami dynamicznymi, możemy
przechowywać je w kontenerach, przekazy-
wać jako argument oraz zwracać jako wartość.
Przykład użycia pokazano na Listingu 5.
Jeżeli występują cykliczne zależności po-
między obiektami, czyli obiekt A wskazuje na
obiekt B, zaś obiekt B na obiekt A, to sprytne
wskaźniki shared_ptr nie zwolnią obiektów,
pomimo tego, że obiekty nie będą już używa-
ne. Ilustracją tego zjawiska jest obiekt ze skła-
dową będącą sprytnym wskaźnikiem na siebie
(składowa używana zamiast this ). Obiekt ten
nie zostanie zwolniony, jeżeli składowa (spryt-
Listing 3. Fragmenty szablonu std::auto_ptr
template < typename T > class auto_ptr {
public :
explicit auto_ptr ( T * p = 0 ) : p_ ( p ) {}
//argument jest zwykłą (a nie stałą) referencją, ponieważ jest zmieniany
//jest to zachowanie nietypowe i należy zwracać na to uwagę
auto_ptr ( auto_ptr & a ) p_ ( a . p_ ) { a . p_ = 0L ; }
//argument nie jest const auto_ptr&, patrz konstruktor kopiujący
auto_ptr & operator = ( auto_ptr & a ) {
if ( p_ ! = a . p_ ) { delete p_ ; p_ = a . p_ ; a . p_ = 0L ; }
return * this ;
}
~ auto_ptr () { delete p_ ; } //usuwa wskazywany obiekt
T & operator * () { return * p_ ; } //dostęp do zarządzanego obiektu
T * operator -> () const { return p_ ; } //dostęp do zarządzanego obiektu
private :
T * p_ ;
};
����
����
���
���
���
���
���
���
Rysunek 2. Cykliczna zależność pomiędzy obiektami. Sprytny wskaźnik nie zwalnia obiektu
www.sdjournal.org
41
441721404.013.png 441721404.014.png 441721404.015.png 441721404.016.png 441721404.017.png 441721404.018.png 441721404.019.png 441721404.020.png 441721404.021.png 441721404.022.png 441721404.023.png 441721404.024.png 441721404.025.png 441721404.026.png
Programowanie C++
Listing 4. Wykorzystanie std::auto_ptr do zwracania wskaźników do obiektów
ny wskaźnik) zostanie poprawnie zainicjowa-
na, ponieważ będzie , podtrzymywany ' przez tę
składową (patrz Rysunek 2).
Rozwiązaniem w takim przypadku jest
zwolnienie wskaźnika z obowiązku zarządza-
nia obiektem, czyli wywołanie metody reset .
Wystarczy przerwać zależność w jednym miej-
scu, patrz przykład listy cyklicznej przedsta-
wionej na Listingu 7 oraz Rysunku 3.
Automatyczne zwalnianie obiektów, gdy
występują zależności cykliczne, jest możli-
we, jeżeli wykorzystamy odmianę wskaźni-
ków zwaną słabymi sprytnymi wskaźnika-
mi. Takie obiekty przechowują odniesienie
do obiektu i do licznika, ale nie modyfikują
licznika odniesień, więc fakt, że słaby spryt-
ny wskaźnik wskazuje na obiekt, nie wpły-
wa na czas jego życia. Kod słabego sprytne-
go wskaźnika boost::weak_ptr został poda-
ny na Listingu 8.
Słaby sprytny wskaźnik wprowadzony
w dowolne miejsce zależności cyklicznej
sprawia, że obiekty będą zwalniane prawi-
dłowo.
Dla obiektu zawierającego składową
wskazującą na dany obiekt, składowa ta po-
winna być słabym wskaźnikiem (patrz Ry-
sunek 4 oraz Listing 9).
Słaby wskaźnik nie podtrzymuje zarządza-
nego obiektu, więc może się okazać, że obiekt
wskazywany przez taki wskaźnik zostanie
usunięty przez destruktor silnego sprytnego
wskaźnika ( shared_ptr ). Aby uniemożliwić
wystąpienie błędu polegającego na próbie od-
wołania do zwolnionego obiektu dynamicz-
nego, słabe sprytne wskaźniki są powiada-
miane o fakcie usunięcia zarządzanego obiek-
tu, wtedy przechowywany wskaźnik jest zero-
wany i metoda expired będzie zwracać war-
tość true . Z tego powodu nie można inicjo-
wać składowych, które są słabymi sprytnymi
wskaźnikami wskazującymi na obiekt w kon-
struktorze.
auto_ptr < Foo > createFoo () { //funkcja zwraca wskaźnik na obiekt
return auto_ptr < Foo > ( new Foo ( n ) );
}
createFoo (); //wskaźnik usuwany wtedy gdy niszczony obiekt tymczasowy
auto_ptr < Foo > v = createFoo (); //konstruktor kopiujący dla obiektu v
auto_ptr < Foo > w = v ; //teraz v.p_ jest równe nullptr
//wskaźnik usuwany gdy obiekt w wyjdzie z zasięgu
Listing 5. Zarządzanie obiektami dynamicznymi przez wskaźniki shared_ptr
class Base { //klasa bazowa
virtual ~ Base (){}
};
class Derived1 : public Base { };
class Derived2 : public Base { };
//wskaźnik na klasę bazową
typedef shared_ptr < Base > PBase ;
vector < PBase > v ;
v . push_back ( PBase ( new Derived1 ) );
v . push_back ( PBase ( new Derived2 ) );
//destruktor wektora v usunie obiekty utworzone na stercie
Listing 6. Zależność cykliczna, obiekt ma składową, która na niego wskazuje
struct Foo {
shared_ptr < Foo > me_ ;
};
shared_ptr < Foo > pFoo = new Foo ; //licznik równy 1
pFoo -> me_ = pFoo ; //inicjacja składowej, licznik równy 2
//jeżeli teraz pFoo zostanie zniszczone, to licznik jest równy 1
//obiekt nie jest usuwany i nie ma do niego dostępu
�����
�����
�����
�����
�����
�����
Rysunek 3. Lista cykliczna. Destruktor listy musi przerwać cykliczną zależność pomiędzy obiektami,
ponieważ inaczej elementy listy nie będą usunięte przy niszczeniu listy
Sprytne
wskaźniki narzucające interfejs
Umieszczenie licznika odniesień w prze-
chowywanym obiekcie zapobiega próbom
tworzenia niezależnych sprytnych wskaź-
����
����
W Sieci
http://www.boost.org ;
http://www.open-std.org .
���
���
���
���
�������������
������
�������
Rysunek 4. Eliminacja cyklicznej zależności poprzez wprowadzenie słabych sprytnych wskaźników
Rysunek 5. Sprytne wskaźniki z licznikiem
przechowywanym w obiekcie
42
11/2009
441721404.027.png 441721404.028.png 441721404.029.png 441721404.030.png 441721404.031.png 441721404.032.png 441721404.033.png 441721404.034.png 441721404.035.png 441721404.036.png 441721404.037.png 441721404.038.png 441721404.039.png
Sprytne wskaźniki
Listing 7. Lista cykliczna używająca sprytnych wskaźników
ników zarządzających tym obiektem. Do-
datkową zaletą takiego rozwiązania jest
lepsze wykorzystanie pamięci, ponieważ
po pierwsze, nie jest wymagany dodatko-
wy obiekt na stercie (licznik), a po drugie,
sam sprytny wskaźnik ma wielkość taką jak
zwykły wskaźnik. Wadą rozwiązania jest je-
go mniejsza elastyczność, sprytne wskaź-
niki można utworzyć tylko dla klas, któ-
re przechowują licznik (patrz Rysunek 5).
Tego rodzaju sprytnym wskaźnikiem jest
boost::intrusive_ptr .
Klasy, dla których będą tworzone wskaźniki
za pomocą szablonu intrusive_ptr , muszą do-
starczać funkcji intrusive_ptr_add_ref(T*)
i intrusive_ptr_release(T*) , gdzie T jest
nazwą klasy. Sprytne wskaźniki wykorzystują-
ce licznik znajdujący się w obiekcie pozwalają
na wygodną implementację wskaźników, któ-
re mogą być współdzielone przez różne wątki,
ponieważ obiekt może dostarczać mechani-
zmów synchronizujących patrz (Listing 11).
class List { //lista cykliczna, patrz rysunek 3
struct Node { //węzeł dla listy
typedef shared_ptr < Node > PNode ;
Node ( PNode next ) : next_ ( next ) { }
~ Node () { }
PNode next_ ; //wskaźnik na element następny
};
public :
List () {}
~ List () { //jawnie przerywa zależność cykliczną
if ( tail_ ) tail_ -> next_ . reset ();
} //destruktory obiektów head_ oraz tail_ skasują elementy listy
private :
Node :: PNode head_ , tail_ ;
};
Listing 8. Słaby sprytny wskaźnik
template < class T > class weak_ptr {
public :
//konstruktor na podstawie silnego sprytnego wskaźnika
template < class Y > weak_ptr ( shared_ptr < Y > const & r );
template < class Y > weak_ptr ( weak_ptr < Y > const & r );
~ weak_ptr ();
bool expired () const ; //informacja o zwolnieniu zarządzanego obiektu
shared_ptr < T > lock () const ; //tworzy silny wskaźnik do danego obiektu
//pozostałe metody
};
Podsumowanie
Sprytne wskaźniki wykorzystuje się w C++
do zarządzania czasem życia obiektów
utworzonych na stercie. Język ten nie do-
starcza innego standardowego mechanizmu
tego typu. Wskaźniki takie są wygodne i
pozwalają na uproszczenie kodu, zwłaszcza
wtedy, gdy dopuszczamy możliwość wystą-
pienia wyjątku, czyli przerwania sekwencyj-
nego ciągu instrukcji. Standard oraz biblio-
teki boost dostarczają tego rodzaju udo-
godnienia. Sprytne wskaźniki zajmują tyle
samo pamięci co zwykłe wskaźniki ( sco-
ped_ptr , auto_ptr i intrusive_ptr ), lub
niewiele więcej ( shared_ptr i weak_ptr ),
narzuty czasowe są minimalne, jedno po-
średnie odwołanie dla sprytnych wskaźni-
ków będących wyłącznymi posiadaczami
obiektów oraz badanie i modyfikacja liczni-
ka dla sprytnych wskaźników z licznikiem
odniesień.
Sprytne wskaźniki możemy wykorzy-
stywać w aplikacjach współbieżnych. Jeże-
li ten sam obiekt dynamiczny będzie wska-
zywany przez sprytne wskaźniki znajdują-
ce się w różnych wątkach, to należy użyć
współbieżnej wersji wskaźników, która
zapewnia synchronizację pomiędzy ope-
racjami na liczniku i operacją zwalniania
obiektu.
Listing 9. Zapobieganie zależnościom cyklicznym na przykładzie składowej wskazującej na obiekt
struct Foo {
weak_ptr < Foo > me_ ;
};
shared_ptr < Foo > pFoo = new Foo ; //licznik równy 1
pFoo -> me_ = pFoo ; //licznik równy 1, bo słaby wskaźnik
//gdy pFoo zostanie zniszczone, to obiekt jest usuwany
Listing 10. Klasa dostarczająca interfejsu używanego przez intrusive_ptr
//klasa której obiekty będą zarządzane przez sprytne wskaźniki współdzielone przez
różne wątki
class Foo {
friend void intrusive_ptr_add_ref ( Foo * ptr );
friend void intrusive_ptr_release ( Foo * ptr );
boost :: mutex m_ ; //obiekt synchronizujący
int counter_ ; //licznik dla intrusive_ptr
// pozostałe metody i składowe
};
void intrusive_ptr_add_ref ( Foo * foo ) {
mutex :: scoped_lock lock ( foo -> m_ ); //sekcja krytyczna
++ ( foo -> counter_ );
}
void intrusive_ptr_release ( Foo * foo ) {
bool del = false ;
{
mutex :: scoped_lock lock ( foo -> m_ ); //sekcja krytyczna
del = ! -- ( foo -> counter_ );
} //laga del pozwala kasować obiekt poza sekcją krytyczną
if ( del ) delete foo ;
}
ROBERT NOWAK
Adiunkt w Zakładzie Sztucznej Inteligencji Insty-
tutu Systemów Elektronicznych Politechniki War-
szawskiej, zainteresowany tworzeniem aplika-
cji dla biologii i medycyny, programuje w C++ od
ponad 10 lat.
Kontakt z autorem:rno@o2.pl
www.sdjournal.org
43
441721404.040.png 441721404.041.png 441721404.042.png 441721404.043.png 441721404.044.png 441721404.045.png 441721404.046.png 441721404.047.png 441721404.048.png 441721404.049.png 441721404.050.png
 
Zgłoś jeśli naruszono regulamin