Przykład użycia shared_ptr?


82

Cześć Zadałem dziś pytanie o to, jak wstawiać różne typy obiektów w tej samej tablicy wektorowej, a mój kod w tym pytaniu był

 gate* G[1000];
G[0] = new ANDgate() ;
G[1] = new ORgate;
//gate is a class inherited by ANDgate and ORgate classes
class gate
{
 .....
 ......
 virtual void Run()
   {   //A virtual function
   }
};
class ANDgate :public gate 
  {.....
   .......
   void Run()
   {
    //AND version of Run
   }  

};
 class ORgate :public gate 
  {.....
   .......
   void Run()
   {
    //OR version of Run
   }  

};      
//Running the simulator using overloading concept
 for(...;...;..)
 {
  G[i]->Run() ;  //will run perfectly the right Run for the right Gate type
 } 

a chciałem użyć wektorów więc ktoś napisał, że powinienem to zrobić:

std::vector<gate*> G;
G.push_back(new ANDgate); 
G.push_back(new ORgate);
for(unsigned i=0;i<G.size();++i)
{
  G[i]->Run();
}

ale potem on i wielu innych zasugerował, że lepiej byłoby użyć kontenerów wskaźnika doładowania
lub shared_ptr. Spędziłem ostatnie 3 godziny na czytaniu na ten temat, ale dokumentacja wydaje mi się dość zaawansowana. **** Czy ktoś może mi podać mały przykład shared_ptrużycia kodu i dlaczego zasugerował użycie shared_ptr. Są tam również inne typy, takie jak ptr_vector, ptr_listi ptr_deque** **

Edit1: Przeczytałem też przykład kodu, który obejmował:

typedef boost::shared_ptr<Foo> FooPtr;
.......
int main()
{
  std::vector<FooPtr>         foo_vector;
........
FooPtr foo_ptr( new Foo( 2 ) );
  foo_vector.push_back( foo_ptr );
...........
}

I nie rozumiem składni!


2
Której składni nie rozumiesz? Pierwsza linia maintworzy wektor, który może zawierać wspólne wskaźniki do typu o nazwie Foo; druga tworzy Fooużycie newi wspólny wskaźnik do zarządzania nim; trzeci umieszcza kopię współdzielonego wskaźnika do wektora.
Mike Seymour

Odpowiedzi:


116

Użycie a vectorz shared_ptreliminuje możliwość wycieku pamięci, ponieważ zapomniałeś przejść przez wektor i wywołać deletekażdy element. Przyjrzyjmy się nieco zmodyfikowanej wersji przykładu wiersz po wierszu.

typedef boost::shared_ptr<gate> gate_ptr;

Utwórz alias dla współdzielonego typu wskaźnika. Pozwala to uniknąć brzydoty języka C ++, która wynika z pisania na klawiaturze std::vector<boost::shared_ptr<gate> >i zapominania o spacji między zamykającymi znakami większości .

    std::vector<gate_ptr> vec;

Tworzy pusty wektor boost::shared_ptr<gate>obiektów.

    gate_ptr ptr(new ANDgate);

Przydziel nowe ANDgatewystąpienie i zapisz je w pliku shared_ptr. Powodem zrobienia tego osobno jest zapobieżenie problemowi, który może wystąpić, jeśli operacja się zgłosi. W tym przykładzie nie jest to możliwe. W sekcji shared_ptr„ Sprawdzone metody Boost wyjaśniono, dlaczego najlepiej jest przydzielić do obiektu wolnostojącego zamiast tymczasowego.

    vec.push_back(ptr);

Tworzy to nowy wspólny wskaźnik w wektorze i kopiuje ptrdo niego. Zliczanie referencji w jelitach shared_ptrzapewnia, że ​​zaalokowany obiekt wewnątrz ptrzostanie bezpiecznie przeniesiony do wektora.

Nie wyjaśniono, że destruktor shared_ptr<gate>zapewnia usunięcie przydzielonej pamięci. W ten sposób unika się wycieku pamięci. Destruktor for std::vector<T>zapewnia, że ​​destruktor for Tjest wywoływany dla każdego elementu przechowywanego w wektorze. Jednak destruktor dla wskaźnika (np. gate*) Nie usuwa przydzielonej pamięci . Tego właśnie próbujesz uniknąć, używając shared_ptrlub ptr_vector.


1
To było szczegółowe :). Moje pytanie dotyczy trzeciej linii kodu gate_ptr ptr (nowy ANDgate); Nie wydaje mi się to zbyt znajome, ptr wspólnej bramki wskaźnikowej typu, a następnie między nawiasami klamrowymi wysłałeś nowy ANDgate! To jest mylące.
Ahmed

6
@Ahmed: ogólne wyrażenie jest inicjalizacją zmiennej, tak jak możesz napisać, int x(5);aby zainicjalizować xwartość 5. W tym przypadku jest ono inicjowane wartością nowego wyrażenia, które tworzy ANDgate; wartością nowego wyrażenia jest wskaźnik do nowego obiektu.
Mike Seymour

42

Dodam, że jedną z najważniejszych rzeczy shared_ptrjest to tylko kiedykolwiek zbudować je z następującą składnią:

shared_ptr<Type>(new Type(...));

W ten sposób „prawdziwy” wskaźnik do Typejest anonimowy dla twojego zakresu i jest utrzymywany tylko przez wskaźnik współdzielony. Dlatego nie będzie możliwe przypadkowe użycie tego „prawdziwego” wskaźnika. Innymi słowy, nigdy tego nie rób:

Type* t_ptr = new Type(...);
shared_ptr<Type> t_sptr ptrT(t_ptr);
//t_ptr is still hanging around!  Don't use it!

Chociaż to zadziała, teraz masz w funkcji Type*wskaźnik ( t_ptr), który znajduje się poza współdzielonym wskaźnikiem. Używanie go w t_ptrdowolnym miejscu jest niebezpieczne , ponieważ nigdy nie wiadomo, kiedy współdzielony wskaźnik, który go trzyma, może go zniszczyć, a ty posypiesz się.

To samo dotyczy wskazówek zwróconych przez inne klasy. Jeśli klasa, której nie napisałeś, podaje Ci wskaźnik, generalnie nie jest bezpiecznie umieścić go w pliku shared_ptr. Nie, chyba że masz pewność, że klasa nie używa już tego obiektu. Ponieważ jeśli umieścisz go w a shared_ptri wypadnie on poza zakres, obiekt zostanie zwolniony, gdy klasa może go nadal potrzebować.


8
Wszystko, co powiedział Ken, jest dobre i prawdziwe, ale uważam, że preferowanym sposobem na określenie tego teraz jest auto t_ptr = make_shared<Type>(...);lub równoważnie shared_ptr<Type> t_ptr = make_shared<Type>(...);, po prostu dlatego, że ta forma jest bardziej wydajna.
Jason Sydes

@KenSimon, czy ,między t_sptri ptrTw powinien być przecinek shared_ptr<Type> t_sptr ptrT(t_ptr);?
Allanqunzi

Oprócz niejasności w przykładowym kodzie, dobre ostrzeżenie - ale szkoda, że ​​musisz to zrobić, ponieważ pierwsza forma jest o wiele czystsza, a może co ważniejsze, z pewnością każdy, kto używa inteligentnego wskaźnika, wie, że istnieje dokładnie po to, aby uniknąć niebezpiecznego surowego wskaźniki pływające wokół. Ostatni akapit jest interesujący; na szczęście nie pracowałem jeszcze z żadną biblioteką, która zmusza mnie do używania punktów typu surowego lub niejasnego, chociaż jestem pewien, że to kiedyś się stanie.
underscore_d

20

Nauka korzystania z inteligentnych wskaźników jest moim zdaniem jednym z najważniejszych kroków, aby zostać kompetentnym programistą C ++. Jak wiesz, za każdym razem, gdy nowy obiekt w pewnym momencie chcesz go usunąć.

Jednym z pojawiających się problemów jest to, że z wyjątkami może być bardzo trudno upewnić się, że obiekt jest wydawany zawsze tylko raz we wszystkich możliwych ścieżkach wykonywania.

To jest powód RAII: http://en.wikipedia.org/wiki/RAII

Stworzenie klasy pomocniczej w celu upewnienia się, że obiekt zawsze jest usuwany raz ze wszystkich ścieżek wykonywania.

Przykład takiej klasy to: std :: auto_ptr

Ale czasami lubisz dzielić się przedmiotami z innymi. Powinien zostać usunięty tylko wtedy, gdy nikt go już nie używa.

Aby pomóc w tych strategiach liczenia referencji, zostały opracowane, ale nadal musisz pamiętać adres i ręcznie zwolnić ref. W istocie jest to ten sam problem, co nowy / usuń.

Dlatego boost opracował metodę boost :: shared_ptr, jest to inteligentny wskaźnik zliczający odwołania, dzięki czemu można udostępniać obiekty i zapobiegać przypadkowemu wyciekowi pamięci.

Wraz z dodaniem C ++ tr1 jest to teraz również dodawane do standardu C ++, ale ma nazwę std :: tr1 :: shared_ptr <>.

Jeśli to możliwe, zalecam używanie standardowego wskaźnika współdzielonego. ptr_list, ptr_dequeue i tak samo są wyspecjalizowanymi kontenerami IIRC dla typów wskaźników. Na razie je ignoruję.

Więc możemy zacząć od twojego przykładu:

std::vector<gate*> G; 
G.push_back(new ANDgate);  
G.push_back(new ORgate); 
for(unsigned i=0;i<G.size();++i) 
{ 
  G[i]->Run(); 
} 

Problem polega na tym, że za każdym razem, gdy G wychodzi poza zasięg, przeciekamy 2 obiekty dodane do G. Przepiszmy to tak, aby używały std :: tr1 :: shared_ptr

// Remember to include <memory> for shared_ptr
// First do an alias for std::tr1::shared_ptr<gate> so we don't have to 
// type that in every place. Call it gate_ptr. This is what typedef does.
typedef std::tr1::shared_ptr<gate> gate_ptr;    
// gate_ptr is now our "smart" pointer. So let's make a vector out of it.
std::vector<gate_ptr> G; 
// these smart_ptrs can't be implicitly created from gate* we have to be explicit about it
// gate_ptr (new ANDgate), it's a good thing:
G.push_back(gate_ptr (new ANDgate));  
G.push_back(gate_ptr (new ORgate)); 
for(unsigned i=0;i<G.size();++i) 
{ 
   G[i]->Run(); 
} 

Kiedy G wychodzi poza zakres, pamięć jest automatycznie przywracana.

Jako ćwiczenie, którym nękałem nowicjuszy w moim zespole, jest poproszenie ich o napisanie własnej klasy inteligentnego wskaźnika. Następnie, gdy skończysz, natychmiast odrzuć klasę i nigdy więcej jej nie używaj. Miejmy nadzieję, że zdobyłeś kluczową wiedzę na temat działania inteligentnego wskaźnika pod maską. Naprawdę nie ma magii.


Mój instruktor dał mi podobną radę co do pisania własnych zajęć, więc na pewno spróbuję. TY.
Ahmed

powinieneś użyć iteratora do uruchomienia wszystkich bramekfor( auto itt = G.begin(); itt != G.end(); ++itt ){ itt->Run(); }
Guillaume Massé

1
Albo jeszcze lepiej, nowy „foreach” w C ++
Kolejny metaprogramator

2

Dokumentacja doładowania dostarcza całkiem niezłego przykładu początkowego: przykład shared_ptr (w rzeczywistości chodzi o wektor inteligentnych wskaźników) lub dokument shared_ptr Poniższa odpowiedź Johannesa Schauba dość dobrze wyjaśnia inteligentne wskaźniki doładowania: wyjaśniono inteligentne wskaźniki

Idea kryjąca się za (w jak najmniejszej liczbie słów) ptr_vector polega na tym, że obsługuje on za Ciebie zwolnienie pamięci za przechowywanymi wskaźnikami: powiedzmy, że masz wektor wskaźników, jak w przykładzie. Zamykając aplikację lub opuszczając zakres, w którym wektor jest zdefiniowany, będziesz musiał posprzątać po sobie (dynamicznie zaalokowałeś ANDgate i ORgate), ale samo wyczyszczenie wektora nie zrobi tego, ponieważ wektor przechowuje wskaźniki a nie rzeczywiste obiekty (nie zniszczy, ale to, co zawiera).

 // if you just do
 G.clear() // will clear the vector but you'll be left with 2 memory leaks
 ...
// to properly clean the vector and the objects behind it
for (std::vector<gate*>::iterator it = G.begin(); it != G.end(); it++)
{
  delete (*it);
}

boost :: ptr_vector <> zajmie się powyższym za Ciebie - co oznacza, że ​​zwolni pamięć za przechowywanymi wskaźnikami.


shared_ptr jest inteligentnym wskaźnikiem - błyszczącym „opakowaniem” zwykłego wskaźnika, który, powiedzmy, dodaje trochę sztucznej inteligencji do typu wskaźnika. ptr_vector to inteligentny kontener na wskaźniki - "opakowanie" na kontener wskaźników.
celavek

więc ptr_vector jest rodzajem zamiany na wektor normalny?
Ahmed

@Ahmed Myślę, że możesz o tym myśleć w ten sposób.
celavek

2

Dzięki Boost możesz to zrobić>

std::vector<boost::any> vecobj;
    boost::shared_ptr<string> sharedString1(new string("abcdxyz!"));    
    boost::shared_ptr<int> sharedint1(new int(10));
    vecobj.push_back(sharedString1);
    vecobj.push_back(sharedint1);

> do wstawiania obiektów innego typu w kontenerze wektorowym. podczas gdy aby uzyskać dostęp, musisz użyć any_cast, który działa jak dynamic_cast, ma nadzieję, że zadziała dla twoich potrzeb.


1
#include <memory>
#include <iostream>

class SharedMemory {
    public: 
        SharedMemory(int* x):_capture(x){}
        int* get() { return (_capture.get()); }
    protected:
        std::shared_ptr<int> _capture;
};

int main(int , char**){
    SharedMemory *_obj1= new SharedMemory(new int(10));
    SharedMemory *_obj2 = new SharedMemory(*_obj1);
    std::cout << " _obj1: " << *_obj1->get() << " _obj2: " << *_obj2->get()
    << std::endl;
    delete _obj2;

    std::cout << " _obj1: " << *_obj1->get() << std::endl;
    delete _obj1;
    std::cout << " done " << std::endl;
}

To jest przykład działania shared_ptr. _obj2 został usunięty, ale wskaźnik jest nadal prawidłowy. wyjście to ./test _obj1: 10 _obj2: 10 _obj2: 10 gotowe


0

Najlepszym sposobem na dodanie różnych obiektów do tego samego kontenera jest użycie pętli make_shared, wektor i zakres, a otrzymasz ładny, czysty i „czytelny” kod!

typedef std::shared_ptr<gate> Ptr   
vector<Ptr> myConatiner; 
auto andGate = std::make_shared<ANDgate>();
myConatiner.push_back(andGate );
auto orGate= std::make_shared<ORgate>();
myConatiner.push_back(orGate);

for (auto& element : myConatiner)
    element->run();
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.