{{smart_ptr_latin2.cpp|}} * * Autor: Marcin Szewczyk, 198370 * * Temat: smart_ptr * * Inteligentne wskaźniki zawarte w module smart_ptr biblioteki boost. * Sposób ich wykorzystania, różnice pomiędzy poszczególnymi typami wskaźników, * nieco ciekawostek, które nie rzucają się od razu w oczy. * * Chociaż ten plik jest dość długi zawiera tylko garść informacji. Pełna * dokumentacja, na której opierałem się pisząc ten program jest tu: * http://www.boost.org/doc/libs/1_35_0/libs/smart_ptr/smart_ptr.htm * * Omówione tutaj typy inteligentnych wskaźników to: * - scoped_ptr * - scoped_array * - shared_ptr * - weak_ptr * - intrusive_ptr * - oraz std::auto_ptr nienależący do bibl. boost, ale warty porównania * * nie został opisany typ shared_array, ponieważ nie wnosi nic nowego do * omawianych tu zagadnień * * Changelog: * 2008.10.18 W końcu poprawiłem mały wyciek pamięci w miejscu, w którym * nie użyłem smart_ptr - o ironio! * * Dobre rady: * W tym dokumencie prezentowany jest wyciek pamięci w przypadku cykli * budowanych na shared_ptr i jego brak w przypadku cykli budowanych na * weak_ptr. Można uzyskać dobrą wizualizację zagadnienia przy pomocy * aplikacji valgrind, * Proponowany sposób użycia: * valgrind --leak-check=full ./smart_ptr > /dev/null * Efekt: * 114 (12 direct, 102 indirect) bytes in 1 blocks are definitely lost in loss record 3 of 5 * at 0x4023294: operator new(unsigned) (vg_replace_malloc.c:224) * by 0x8049AAE: shared_cycle() (smart_ptr.cpp:474) * by 0x804B5C6: main (smart_ptr.cpp:608) * * Support: * wodny@thlen.pl <- usuń h */ #include #include #include #include #include #include #include #include #include #include #include // ========================================================== // Klasy potrzebne do prezentacji podstawowych cech inteligentnych wskaźników class Harry { public: Harry(const char* = NULL); ~Harry(); void hello(); private: char *name; }; class Betty : public Harry { public: Betty(); ~Betty(); }; // ---------------------------------------------------------- Harry::Harry(const char *name_) { name = name_ ? strdup(name_) : NULL; printf("-- Constructing Harry (%s)\n", name); } Harry::~Harry() { printf("-- Deconstructing Harry (%s)\n", name); free(name); } void Harry::hello() { printf("-- Harry \"%s\" says hello\n", name); } // ---------------------------------------------------------- Betty::Betty() { printf("---- Constructing Betty\n"); } Betty::~Betty() { printf("---- Deconstructing Betty\n"); } // ========================================================== // Klasa, która posłuży do prezentacji, jak radzą sobie inteligentne wskaźniki z // cyklami // // Wariant dla cyklu z użyciem shared_ptr class RingNode { public: RingNode(const char*, const boost::shared_ptr&); ~RingNode(); void connect(const boost::shared_ptr&); const char* get_name(); const boost::shared_ptr& get_node(); private: boost::shared_ptr node; char* name; }; RingNode::RingNode(const char* name_, const boost::shared_ptr& node_) : node(node_) { name = name_ ? strdup(name_) : NULL; printf("++ Constructing RingNode (%s)\n", name); } RingNode::~RingNode() { printf("++ Deconstructing RingNode (%s)\n", name); free(name); } void RingNode::connect(const boost::shared_ptr& node_) { node = node_; } const char* RingNode::get_name() { return name; } const boost::shared_ptr& RingNode::get_node() { return node; } // ---------------------------------------------------------- // Wariant cyklu z weak_ptr class WeakRingNode { public: WeakRingNode(const char*, const boost::shared_ptr&); ~WeakRingNode(); void connect(const boost::shared_ptr&); const char* get_name(); const boost::shared_ptr get_node(); private: boost::weak_ptr node; char* name; }; WeakRingNode::WeakRingNode(const char* name_, const boost::shared_ptr& node_) : node(node_) { name = name_ ? strdup(name_) : NULL; printf("++ Constructing RingNode (%s)\n", name); } WeakRingNode::~WeakRingNode() { printf("++ Deconstructing RingNode (%s)\n", name); free(name); } void WeakRingNode::connect(const boost::shared_ptr& node_) { node = node_; } const char* WeakRingNode::get_name() { return name; } const boost::shared_ptr WeakRingNode::get_node() { return node.lock(); } // ========================================================== // Klasa potrzebna do prezentacji intrusive_ptr // czyli sama zwiększa/zmniejsza ilość referencji do danego obiektu na zlecenie // intrusive_ptr oraz sama odpowiada za zniszczenie obiektu, gdy ilość odwołań // spadnie do zera class Intruded { public: Intruded(); ~Intruded(); private: int counter; friend void intrusive_ptr_add_ref(Intruded *const); friend void intrusive_ptr_release(Intruded *const); }; void intrusive_ptr_add_ref(Intruded *const); void intrusive_ptr_release(Intruded *const); // ---------------------------------------------------------- Intruded::Intruded() : counter(0) { printf("%% Creating Intruded\n"); } Intruded::~Intruded() { printf("%% Destroying Intruded\n"); } // Jak widać, licznik jest składową klasy, natomiast funkcje odpowiedzialne za // zliczanie - nie. // Na podstawie typu parametru, intrusive pointer wybiera odpowiedni zestaw // funkcji do inkrementacji i dekrementacji ilości odwołań void intrusive_ptr_add_ref(Intruded *const p) { printf("%% Added. Now: %d\n", ++(p->counter)); } void intrusive_ptr_release(Intruded *const p) { printf("%% Removed. Now: %d\n", --(p->counter)); if(!p->counter) { printf("%% Deleting Intruded because of count == 0...\n"); delete p; } } // ========================================================== // Jak uzyskać poprawny (pod względem zliczania odwołań) shared_ptr do obiektu // nie mając wiedzy na temat tego, czy już jakiś inny shared_ptr się do niego // odwołuje // Konieczne jest dziedziczenie klasy po enable_shared_from_this<> class WithSharing : public boost::enable_shared_from_this { public: boost::shared_ptr get_shared() { return shared_from_this(); } }; // ========================================================== // ========================================================== // ========================================================== using namespace std; // By uniknąć bardzo długich definicji poniżej typedef vector< boost::shared_ptr > SharedHarryVector; // ========================================================== // Funkcja wyświetla nagłówek tekstowy podkreślony linijką void header(const char* label) { char* bar = strdup(label); cout << endl << label << endl; memset(bar, '=', strlen(bar)); cout << bar << endl; free(bar); } // ========================================================== // ========================================================== // Funkcja jest wywoływana, ale szkodliwy fragment jest wykomentowany void play_dirty_harry() { header("Dirty Harry - czego ze wskaznikami robic nie wolno"); Harry* tmp_harry; boost::scoped_ptr harry(tmp_harry = new Harry("Simple Class Harry")); //boost::scoped_ptr harry2(tmp_harry); // Nielegalne - dwa scoped_ptr, każdy z liczbą odwołań = 1 // Podczas destrukcji dwóch scoped_ptr dochodzi dwa razy do próby destrukcji // jednego obiektu klasy Harry. Lepiej przedstawi to shared_ptr, który pojawi // się poniżej //boost::scoped_ptr harry_b(harry); // Kopiowanie scoped_ptr jest zakazane (konst. kopiujący w sekcji private) //boost::scoped_ptr harry_c; //harry_c = harry; // Analogiczna uwaga dotyczy operatora przypisania } void play_nice_harry() { header("play_nice_harry - scoped_ptr jak zmienna automatyczna"); // Jakieś tam zadania // ... // Tutaj proste zastosowanie - scoped_ptr zachowuje się jak zmienna // automatyczna. Po wyjściu poza zasięg, scoped_ptr niszczy siebie, a // wcześniej związany z nim obiekt. // // Stąd nazwa - scoped - czyli dot. zasięgu. // Nawiązuje to do pomysłu "resource acquisition is initialization". Zasoby // są zajmowane w momencie inicjalizacji i, co ważniejsze, zwalniane podczas // "odinicjowania", czyli destrukcji po wyjściu poza zasięg. boost::scoped_ptr harry3(new Harry("Harry3")); harry3->hello(); Harry harry4("Harry4"); harry4.hello(); } void play_nice_harry3() { // Jednak nie zawsze wystarcza nam zmienna automatyczna. Ciekawsze jest // dynamiczne powoływanie obiektów do życia operatorem "new". header("play_nice_harry3 - operator \"new\" bez potrzeby wywolania delete"); boost::scoped_ptr harry7; // // Coś tam do roboty... bool needHarry = true; // Coś tam do roboty... // if(needHarry) harry7.reset(new Harry("Harry7")); // // Coś tam do roboty... // if(harry7) { printf("-- Stwierdzlismy, ze harry7 sie przyda - wykonajmy na nim operacje\n"); // Operacje na Harrym... } // Normalnie w tym miejscu przydałoby się wywołanie delete na zwykłym // wskaźniku. Ponieważ jednak nasz inteligenty wskaźnik jest zmienną // automatyczną, sam usunie siebie i skojarzony obiekt } void comfort_test() throw(exception) { header("Comfort test - shared_ptr jak zm. auto.; auto. usuwanie, gdy rzucany jest wyjatek"); boost::shared_ptr h1; boost::shared_ptr h2; // Coś tam... // Potrzebujemy Harry'ego h1.reset(new Harry("Potter")); h2.reset(new Harry("Callahan")); cout << "Callahan count: " << h2.use_count() << endl; h1 = h2; cout << "Callahan zjadl Pottera, count: " << h2.use_count() << endl; cout << "h1: "; h1->hello(); cout << "h2: "; h2->hello(); // Go ahead, make my day. // Rzucamy wyjątek cout << "Throwing an exception .44" << endl; throw exception(); } // Prezentacja, jak zachowują się intrusive_ptr void test_intrusive_ptr() { header("test_intrusive_ptr"); boost::intrusive_ptr i1(new Intruded); } // Ciekawostka związana z shared_ptr // Różnica, między tym, co deklarujemy jako klasę dla szablonu shared_ptr, a // klasą parametru przekazywanego w konstruktorze. Jakie nieoczywiste zdarzenie // może wystąpić, gdy destruktory klas są niewirtualne. void non_virt_diffs() { header("Niewirtualny destruktor. Roznice miedzy zwyklym wskaznikiem a shared_ptr"); // Destructor nie jest wirtualny, stąd Betty nie jest niszczona { cout << "Zwykly Harry-wskaznik na Betty" << endl; Harry *hb = new Betty; delete hb; } // Nieco inaczej sprawa się przestawia dla shared_ptr { cout << endl << "Betty-wskaznik na Betty w Harry-szablonie" << endl; boost::shared_ptr b1(new Betty); // shared_ptr pamięta rzeczywisty typ wskaźnika przekazywanego w // parametrze. W związku z tym używa destruktora dla tego typu (Betty) // zamiast typu deklarowanego w szablonie (Harry) // > przykładowa implementacja mechanizmu: // > http://wodny.org/download/smart_ptr/template_memory.cpp } { cout << endl << "Harry-wskaznik na Betty w Harry-szablonie" << endl; Harry *b3 = new Betty; boost::shared_ptr b2(b3); // Tutaj shared_ptr nie ma okazji poznać prawdziwego typu obiektu. // Traktuje go jak Harry'ego. Dla poprawnego działania, tu destruktor już // musi być wirtualny. } } void play_scoped_array() { // Prosty przykład, że inteligentne wskaźniki z dopiskiem _array służą do // kojarzenia ich z tablicami. Nie można by tu użyć scoped_ptr. Jest to // związane z obostrzeniem, że delete stosujemy dla pojedynczego obiektu, // delete[] dla tablic. Inteligenty wskaźnik nie posiadając informacji o // naturze przekazanego wskaźnika źle zarządzałby pamięcią. header("Zabawy ze scoped_array"); boost::scoped_array harry_a1(new Harry[3]); } void general_use() { header("Ogolny sposob uzycia shared_ptr"); Harry *hp1 = NULL; boost::shared_ptr harry_sp1(hp1 = new Harry("Shared Harry1")); cout << "harry_sp1.use_count: " << harry_sp1.use_count() << endl; //boost::shared_ptr harry_sp2(hp1); // Taka konstrukcja spowoduje błąd. Dwa shared_ptr będą próbowały usunąć dwa // razy ten sam obiekt podczas własnej destrukcji. // // Istotne jest, by zauważyć, że shared_ptr nie ma jakiejś ukrytej tablicy // asocjacyjnej (trzymającej się np. wzorca Singleton), która pozwalałaby mu // stwierdzać, którymi wskaźnikami już się opiekuje. // Poinformowanie o tym, że kolejny shared_ptr chce również odnosić się do // danego zwykłego wskaźnika następuje przez wykonanie konstruktora // kopiującego (opcjonalnie operatora przypisania). Od tej pory każdy z // shared_ptr ustawi ilość odwołań na 2 i przy destrukcji jednego z nich, // drugi inteligenty wskaźnik dowie się o tym zdarzeniu i zdekrementuje // własny licznik odwołań // (Mowa jest tu o logice działania, nie rzeczywistej implementacji // inteligentnego wskaźnika). cout << "Czas, by podzielic sie zasobem" << endl; boost::shared_ptr harry_sp2(harry_sp1); boost::shared_ptr harry_sp3; harry_sp3 = harry_sp2; cout << "harry_sp1.use_count: " << harry_sp1.use_count() << endl; cout << "harry_sp2.use_count: " << harry_sp2.use_count() << endl; cout << "harry_sp3.use_count: " << harry_sp3.use_count() << endl; } SharedHarryVector* get_harries() { // Shared_ptr a kontenery // (scoped_ptr nie może zostać użyty do takich zastosowań - nie da się // skopiować kontenerowi, poza tym jego przeznaczenie jest inne) header("Wektor (vector) z Harrych - uzycie shared_ptr w kontenerach STL"); SharedHarryVector *vect = new SharedHarryVector(); boost::shared_ptr h; for(int i = 0; i < 4; ++i) { h.reset(new Harry("Vector Harry")); cout << "Ilosc Harry'ego przed wlozeniem do wektora: " << h.use_count() << endl; vect->push_back(h); cout << "Po wlozeniu: " << h.use_count() << endl; } return vect; } void stl_container() { // W nawiązaniu do powyższej funkcji boost::shared_ptr< SharedHarryVector > harry_vector(get_harries()); cout << "Liczność jednego z wektorowych Harrych: " << harry_vector->front().use_count() << endl << endl; } void no_cycle() { header("Bez cyklu"); // Bez cyklu boost::shared_ptr R3(new RingNode("RingNode3", boost::shared_ptr((RingNode*)NULL))); boost::shared_ptr R4(new RingNode("RingNode4", boost::shared_ptr(R3))); cout << "R3 (" << R4->get_name() << ") with count " << R3.use_count() << endl; cout << "R4 (" << R4->get_name() << ") with count " << R4.use_count() << " connected to " << R4->get_node()->get_name() << endl; // Wszystkie węzły ulegną zniszczeniu jak powinny } void shared_cycle() { header("Cykl na shared_ptr"); // Większy cykl, choć uzyskany tylko pośrednio, powoduje, że węzły nie // zostaną usunięte, ponieważ ilość odwołań w shared_ptr nie spadnie do zera boost::shared_ptr R7(new RingNode("RingNode7", boost::shared_ptr((RingNode*)NULL))); boost::shared_ptr R8(new RingNode("RingNode8", boost::shared_ptr(R7))); boost::shared_ptr R9(new RingNode("RingNode9", boost::shared_ptr(R8))); R7->connect(R9); cout << "R7 (" << R7->get_name() << ") with count " << R7.use_count() << " connected to " << R7->get_node()->get_name() << endl; cout << "R8 (" << R8->get_name() << ") with count " << R8.use_count() << " connected to " << R8->get_node()->get_name() << endl; cout << "R9 (" << R9->get_name() << ") with count " << R9.use_count() << " connected to " << R9->get_node()->get_name() << endl; /* R7-->"R7"<--------node--------. | | *--node-->"R8"<--R8 | | | *--node-->"R9"<--R9 */ } void weak_cycle() { header("Cykl na weak_ptr"); // "Słaby" cykl oparty o weak_ptr boost::shared_ptr R5(new WeakRingNode("WeakRingNode5", boost::shared_ptr((WeakRingNode*)NULL))); boost::shared_ptr R6(new WeakRingNode("WeakRingNode6", boost::shared_ptr(R5))); R5->connect(R6); boost::weak_ptr Wtmp1(R5); boost::weak_ptr Wtmp2(R6); // Jak można zauważyć, weak_ptr nie zwiększa licznika odwołań do obiektu, // którego dotyczy, ani nie usuwa obiektu, gdy wielkość ta spadnie do zera. // Obiekt, do którego się odwołuje może zostać usunięty "bez jego wiedzy". // Dlatego właśnie współpracuje z shared_ptr. cout << "R5 (" << R5->get_name() << ") with count " << R5.use_count() << " connected to " << R5->get_node()->get_name() << endl; cout << "R6 (" << R6->get_name() << ") with count " << R6.use_count() << " connected to " << R6->get_node()->get_name() << endl; // Poprzez funkcję weak_ptr.lock(), która użyta jest w get_node(), weak_ptr // gwarantuje nam, że zostanie wykonana dodatkowa kopia, a shared_ptr zajmie // się zwiększeniem licznika odwołań, by obiekt nie zniknął podczas // wykonywania na nim operacji. // Jeśli obiekt zdążył zniknąć już wcześniej, podczas tworzenia shared_ptr z // weak_ptr dostaniemy wyjątek bad_weak_ptr. } void shared_and_weak() { header("Relacja miedzy shared_ptr i weak_ptr"); cout << "Oryginalny shared_ptr:" << endl; boost::shared_ptr shared1(new Harry); cout << "shared1.count: " << shared1.use_count() << endl; cout << "weak_ptr z shared_ptr:" << endl; boost::weak_ptr weak1(shared1); cout << "shared1.count: " << shared1.use_count() << endl; cout << "weak1.count : " << weak1.use_count() << endl; cout << "shared_ptr z weak_ptr (z shared_ptr)" << endl; boost::shared_ptr shared2(weak1); cout << "shared1.count: " << shared1.use_count() << endl; cout << "weak1.count : " << weak1.use_count() << endl; cout << "shared2.count: " << shared2.use_count() << endl; } void with_sharing_test(WithSharing& ws) { header("Test enable_shared_from_this<>"); boost::shared_ptr ws_a(ws.get_shared()); cout << "Drugi udzial, use_count: " << ws_a.use_count() << endl; /* . * /|\ * | * ------------------------------------------------- * dwa niezależne fragmenty kodu, np. dwa wątki * ------------------------------------------------- * | * \|/ * ' */ boost::shared_ptr ws_b(ws.get_shared()); cout << "Trzeci udzial, use_count: " << ws_b.use_count() << endl; } void auto_ptr_demo() { header("auto_ptr - przy boost maly sens stosowania"); /* * http://gcc.gnu.org/onlinedocs/libstdc++/libstdc++-html-USERS-4.0/classstd_1_1auto__ptr.html * * "auto_ptr does not meet the CopyConstructible and Assignable * requirements for Standard Library container elements and thus * instantiating a Standard Library container with an auto_ptr results in * undefined behavior." * * Reasumując - auto_ptr ciekawy nie jest. Potrafi tylko oddawać powiązanie z * obiektem, natomiast nie potrafi się nim dzielić. Zastosowanie w * kontenerach nie jest możliwe. */ auto_ptr a1(new Harry); cout << "NEW. " << a1.get() << endl; auto_ptr a2(a1); cout << "1st transfer, source: " << a1.get() << endl; cout << "1st transfer, dest. : " << a2.get() << endl; auto_ptr a3 = a2; cout << "2nd transfer, source: " << a2.get() << endl; cout << "2nd transfer, dest. : " << a3.get() << endl; } // ========================================================== // ========================================================== // ========================================================== int main(int argc, char** argv) { // ========================================================== // Zestaw tego, czego robić nie wolno ze scoped_ptr play_dirty_harry(); // shared_ptr general_use(); // Mniej interesujące play_nice_harry(); // Bardziej interesujące play_nice_harry3(); // scoped_array zamiast scoped_ptr - właściwe delete, konkretnie delete[] play_scoped_array(); // Niewirtualny destruktor. Różnice między zwykłym wskaźnikiem a shared_ptr non_virt_diffs(); // ========================================================== // Kontener STL stl_container(); // ========================================================== // Shared vs Weak a cykle no_cycle(); shared_cycle(); weak_cycle(); shared_and_weak(); // ========================================================== // GŁÓWNY POWÓD UŻYWANIA SMART_PTRów // komfort operatora "new" // z bezpieczeństwem zmiennych automatycznych // ========================================================== try { comfort_test(); } catch (...) { cout << "Caught!" << endl; } // ========================================================== // Test enable_shared_from_this<> boost::shared_ptr ws(new WithSharing); with_sharing_test(*ws); // ========================================================== // std::auto_ptr auto_ptr_demo(); // ========================================================== // Intrusive test test_intrusive_ptr(); return 0; }