Posts tagged ‘shared_ptr’

Wszyscy programujący w c/c++ wiedzą, że zarządzanie pamięcią to nie jest sprawa prosta. Załóżmy, że mamy sobie obiekt grupujący jakies pola, powiedzmy, że będzie to klasa Header, i ma mapę Fieldów  (std::map<std::string,Field>) identyfikowanych po nazwie. No i chcemy, żeby Field’y można było modyfikować poza Header’em, czyli mapa musi być dostępna przez publiczne api. Możnaby coprawda udostępnić tylko kopię referencji w mapie, ale tracimy cała elastyczność związaną z bezpośrednimi modyfikacjami Fieldów. No tak, tylko teraz mamy iterację po wskaźnikach na obiekty Field, przykładowy kod może wyglądać tak:

for( FieldMap::iterator it = fieldMap.iterator(); it != fieldMap.end(); it++ ) {

Field* f = *it;

}

Wygląda prosto, ale co jeżeli ktoś zechce nam taki wskaźnik usunąć, albo zastąpić go innym wskaźnikiem? To tylko 2 przykładowe problemy z jakimi sie można napotkać stosując powyższe rozwiązanie. Pewnie zaraz puryści dodadzą, żeby powstawiać całą masę const‚ów, które owszem rozwiążą część problemów, ale same stworzą kolejne.

Zamiast żonglować const’ami, można zastosować smart pointer’y. Czymże one sie charakteryzują – otóż w swej najprostszej postaci, przewowuja nam wskaźnik i udostepniają na żądanie, usuwając go w momencie usunięcia samego siebie (std::auto_ptr) + przechowują prawo własności do wskaźnika. W bardziej skomplikowanej postaci, posiadają wewnętrzny licznik referencji, dzięki któremu mogą stwierdzić czy dany obiekt nadaje sie do usunięcia czy nie (boost::shared_ptr). Żeby jeszcze sobie uprościć życie, można zezwolić na konstrukcję „pustych” obiektów shared_ptr i wypełniać je wskaźnikami dopiero na żadanie.

To w sumie tyle wprowadzenia, po więcej odsyłam do dokumentacji boost’a – nie będe jej tutaj powielał, natomiast moim celem jest przekazanie paru problemów, z którymi sie napotkałem podczas pracy z boost::shared_ptr.

1. Konstruktor kopiujący a nieusuwalny boost::shared_ptr.

W manualu boost’owym podają, że jeżeli potrzebujemy stworzyć inteligentny wskaźnik, który nie będzie usuwany, bo np zarządzamy nim gdzie indziej, a referencji z jakiegoś względu nie możemy przekazać – można do konstruktora przesłać jako dodatkowy argument funktor, który zapewni nam, że podczas dojścia licznika referencji do 0, nic się nie stanie.

Uzbrojeni w taka broń smiało przekazywaliśmy nasz obiekt to tu to tam, aż w pewnym momencie ni z gruchy ni z pietruchy pokazał sie  seg fault. Otóż okazało się, że nasz konstruktor kopiujący owszem kopiował wskaźnik, zwiększał licznik referencji, ale o zgrozo, NIE KOPIOWAŁ funktora, przez co nasz nieusuwalny wskaźnik stawał sie zwykłym shared_ptr’em i w momencie dojścia licznika do 0, został skasowany.

2. Nasza przykładowa aplikacja udostępnia swoją mapę, światowi zewnętrznemu, ale tenże swiat ingerując w stan jej elementów, może nam popsuć sporo i aby temu zaradzić można wprowadzić obserwatora, w momencie dodania fielda, kontener (w tym przypadku Header) rejestruje sie jako obserwator fieldów, a one wysyłają notyfikację, jak ktoś lub coś je zmienia.

Problem jaki się pojawił był związany właśnie z obserwatorem. Otóż w pewnym momencie pochodne Headera służyły jednynie za twórcę mapy, która miała być przekazana dalej, jednak rejestrowały sie jako obserwatory, a w momencie usunięcia nie wyrejestrowywały się, więc mapa lądowała w innej strukturze, ze wskaźnikiem na zwolniony obszar w pamięci.

Dodanie autousuwania obserwatora ze wszystkich fieldów w destruktorze naprawiło sytuację.

Ten krótki artykuł nie jest próbą dyskredytacji smart pointerów a jedynie, formą dzielenia się wiedzą, bo skoro zdażyło sie to raz, to pewnie komuś sie zdarzy drugi raz.

Pozdrawiam