Beispiel für die Verwendung von shared_ptr?

82

Hallo, ich habe heute eine Frage zum Einfügen verschiedener Objekttypen in dasselbe Vektorarray gestellt. Mein Code in dieser Frage war

 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
 } 

und ich wollte Vektoren verwenden, also schrieb jemand, dass ich das tun sollte:

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();
}

aber dann schlugen er und viele andere vor, dass ich besser Boost-Zeigercontainer
oder verwenden sollte shared_ptr. Ich habe die letzten 3 Stunden damit verbracht, über dieses Thema zu lesen, aber die Dokumentation scheint mir ziemlich fortgeschritten zu sein. **** Kann mir jemand ein kleines Codebeispiel für die shared_ptrVerwendung geben und warum sie die Verwendung vorgeschlagen haben shared_ptr. Auch gibt es andere Typen wie ptr_vector, ptr_listund ptr_deque** **

Edit1: Ich habe auch ein Codebeispiel gelesen, das Folgendes enthielt:

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 );
...........
}

Und ich verstehe die Syntax nicht!

Ahmed
quelle
2
Welche Syntax verstehst du nicht? Die erste Zeile mainerzeugt einen Vektor, der genannten Shared - Zeiger auf einen Typ enthalten kann Foo; Der zweite erstellt eine FooVerwendung newund einen gemeinsamen Zeiger, um sie zu verwalten. Der dritte fügt eine Kopie des gemeinsam genutzten Zeigers in den Vektor ein.
Mike Seymour

Antworten:

116

Durch die Verwendung vectorvon a shared_ptrwird die Möglichkeit eines Speicherverlusts beseitigt, da Sie vergessen haben, den Vektor zu durchlaufen und deletejedes Element aufzurufen . Lassen Sie uns eine leicht modifizierte Version des Beispiels Zeile für Zeile durchgehen.

typedef boost::shared_ptr<gate> gate_ptr;

Erstellen Sie einen Alias ​​für den gemeinsam genutzten Zeigertyp. Dies vermeidet die Hässlichkeit in der C ++ - Sprache, die sich aus der Eingabe std::vector<boost::shared_ptr<gate> >und dem Vergessen des Leerzeichens zwischen den schließenden Größer-als-Zeichen ergibt .

    std::vector<gate_ptr> vec;

Erstellt einen leeren Vektor von boost::shared_ptr<gate>Objekten.

    gate_ptr ptr(new ANDgate);

Ordnen Sie eine neue ANDgateInstanz zu und speichern Sie sie in einer shared_ptr. Der Grund dafür ist, ein Problem zu vermeiden, das auftreten kann, wenn eine Operation ausgelöst wird. Dies ist in diesem Beispiel nicht möglich. Die Boost - shared_ptr„Best Practices“ erklären , warum es dich um eine Best - Practice in ein freistehendes Objekt zuweisen anstelle einem zeitlich begrenzt.

    vec.push_back(ptr);

Dadurch wird ein neuer gemeinsamer Zeiger im Vektor erstellt und ptrin diesen kopiert . Die Referenzzählung in den Eingeweiden von shared_ptrstellt sicher, dass das zugewiesene Objekt innerhalb von ptrsicher in den Vektor übertragen wird.

Was nicht erklärt wird, ist, dass der Destruktor für shared_ptr<gate>sicherstellt, dass der zugewiesene Speicher gelöscht wird. Hier wird der Speicherverlust vermieden. Der Destruktor für std::vector<T>stellt sicher, dass der Destruktor für Tfür jedes im Vektor gespeicherte Element aufgerufen wird. Der Destruktor für einen Zeiger (z. B. gate*) löscht jedoch nicht den von Ihnen zugewiesenen Speicher . Das versuchen Sie mit shared_ptroder zu vermeiden ptr_vector.

D. Shawley
quelle
1
Das war detailliert :). Meine Frage bezieht sich auf die 3. Codezeile gate_ptr ptr (neues ANDgate); Es kommt mir nicht bekannt vor, ptr eines Typs Shared Pointer Gate und dann zwischen geschweiften Klammern haben Sie ein neues ANDgate gesendet! Das ist verwirrend.
Ahmed
6
@Ahmed: Der Gesamtausdruck ist eine variable Initialisierung, genau wie Sie schreiben könnten, int x(5);um xmit dem Wert 5 zu initialisieren . In diesem Fall wird er mit dem Wert des neuen Ausdrucks initialisiert, der ein ANDgate; Der Wert des neuen Ausdrucks ist ein Zeiger auf das neue Objekt.
Mike Seymour
42

Ich werde hinzufügen, dass eines der wichtigsten Dinge bei shared_ptr's ist, sie immer nur mit der folgenden Syntax zu konstruieren:

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

Auf diese Weise ist der "echte" Zeiger auf Typefür Ihren Bereich anonym und wird nur vom gemeinsam genutzten Zeiger gehalten. Daher können Sie diesen "echten" Zeiger nicht versehentlich verwenden. Mit anderen Worten, tun Sie dies niemals:

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

Obwohl dies funktioniert, haben Sie jetzt einen Type*Zeiger ( t_ptr) in Ihrer Funktion, der sich außerhalb des gemeinsam genutzten Zeigers befindet. Es ist gefährlich, es t_ptrüberall zu verwenden , da Sie nie wissen, wann der gemeinsam genutzte Zeiger, der es enthält, es zerstören kann, und Sie werden einen Fehler machen.

Gleiches gilt für Zeiger, die Ihnen von anderen Klassen zurückgegeben wurden. Wenn eine Klasse, die Sie nicht geschrieben haben, Ihnen einen Zeiger gibt, ist es im Allgemeinen nicht sicher, ihn einfach in eine zu setzen shared_ptr. Nur wenn Sie sicher sind , dass die Klasse dieses Objekt nicht mehr verwendet. Wenn Sie es in a shared_ptreinfügen und es außerhalb des Gültigkeitsbereichs liegt, wird das Objekt freigegeben, wenn die Klasse es möglicherweise noch benötigt.

Ken Simon
quelle
7
Alles, was Ken sagte, ist gut und wahr, aber ich glaube, die bevorzugte Art, es jetzt zu nennen, ist auto t_ptr = make_shared<Type>(...);oder gleichwertig shared_ptr<Type> t_ptr = make_shared<Type>(...);, einfach weil diese Form effizienter ist.
Jason Sydes
@ KenSimon, soll es ein Komma ,zwischen t_sptrund ptrTin geben shared_ptr<Type> t_sptr ptrT(t_ptr);?
Allanqunzi
Abgesehen von den Unklarheiten im Beispielcode, gute Warnung - aber schade, dass Sie es machen müssen, da die 1. Form so viel sauberer ist, und vielleicht noch wichtiger, weiß sicherlich jeder, der einen intelligenten Zeiger verwendet, dass er genau existiert, um gefährliche Rohdaten zu vermeiden Zeiger schweben herum. Der letzte Absatz ist interessant; Zum Glück habe ich noch nicht mit einer Bibliothek gearbeitet, die mich dazu zwingt, rohe oder unklare Punkte zu verwenden, obwohl ich mir sicher bin, dass dies irgendwann passieren wird.
underscore_d
20

Das Erlernen des Gebrauchs intelligenter Zeiger ist meiner Meinung nach einer der wichtigsten Schritte, um ein kompetenter C ++ - Programmierer zu werden. Wie Sie wissen, möchten Sie ein Objekt immer dann löschen, wenn Sie es neu erstellen.

Ein Problem ist, dass es mit Ausnahmen sehr schwierig sein kann, sicherzustellen, dass ein Objekt in allen möglichen Ausführungspfaden immer nur einmal freigegeben wird.

Dies ist der Grund für RAII: http://en.wikipedia.org/wiki/RAII

Erstellen einer Hilfsklasse mit dem Ziel, sicherzustellen, dass ein Objekt in allen Ausführungspfaden immer einmal gelöscht wird.

Ein Beispiel für eine Klasse wie diese ist: std :: auto_ptr

Aber manchmal möchten Sie Objekte mit anderen teilen. Es sollte nur gelöscht werden, wenn es von niemandem mehr verwendet wird.

Um dies zu unterstützen, wurden Referenzzählstrategien entwickelt, aber Sie müssen sich immer noch an addref erinnern und ref manuell freigeben. Im Wesentlichen ist dies das gleiche Problem wie neu / löschen.

Aus diesem Grund hat boost boost :: shared_ptr entwickelt, einen intelligenten Zeiger für die Referenzzählung, mit dem Sie Objekte gemeinsam nutzen und nicht ungewollt Speicher verlieren können.

Mit der Hinzufügung von C ++ tr1 wird dies nun auch zum c ++ - Standard hinzugefügt, jedoch mit dem Namen std :: tr1 :: shared_ptr <>.

Ich empfehle, wenn möglich den gemeinsamen Standardzeiger zu verwenden. ptr_list, ptr_dequeue usw. sind IIRC-spezialisierte Container für Zeigertypen. Ich ignoriere sie jetzt.

Wir können also von Ihrem Beispiel ausgehen:

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(); 
} 

Das Problem hierbei ist nun, dass jedes Mal, wenn G den Gültigkeitsbereich verlässt, die 2 zu G hinzugefügten Objekte verloren gehen. Schreiben wir es neu, um std :: tr1 :: shared_ptr zu verwenden

// 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(); 
} 

Wenn G den Gültigkeitsbereich verlässt, wird der Speicher automatisch zurückgefordert.

Eine Übung, mit der ich Neulinge in meinem Team plagte, ist, sie zu bitten, ihre eigene Smart-Pointer-Klasse zu schreiben. Wenn Sie fertig sind, verwerfen Sie die Klasse sofort und verwenden Sie sie nie wieder. Hoffentlich haben Sie wichtige Kenntnisse darüber erworben, wie ein intelligenter Zeiger unter der Haube funktioniert. Es gibt wirklich keine Magie.

Nur ein weiterer Metaprogrammer
quelle
Mein Lehrer gab mir einen ähnlichen Rat zum Schreiben meiner eigenen Klassen, also werde ich das auf jeden Fall versuchen. TY.
Ahmed
Sie sollten einen Iterator verwenden, um alle Tore auszuführenfor( auto itt = G.begin(); itt != G.end(); ++itt ){ itt->Run(); }
Guillaume Massé
1
Oder noch besser das neue "foreach" in C ++
Nur ein weiterer Metaprogrammer
2

Die Boost-Dokumentation bietet ein ziemlich gutes Startbeispiel : shared_ptr-Beispiel (es handelt sich tatsächlich um einen Vektor von intelligenten Zeigern) oder shared_ptr doc Die folgende Antwort von Johannes Schaub erklärt die Boost-intelligenten Zeiger ziemlich gut: intelligente Zeiger erklärt

Die Idee hinter (in so wenigen Worten wie möglich) ptr_vector ist, dass es die Freigabe des Speichers hinter den gespeicherten Zeigern für Sie übernimmt: Nehmen wir an, Sie haben einen Zeigervektor wie in Ihrem Beispiel. Wenn Sie die Anwendung beenden oder den Bereich verlassen, in dem der Vektor definiert ist, müssen Sie nach sich selbst bereinigen (Sie haben ANDgate und ORgate dynamisch zugewiesen), aber nur das Löschen des Vektors reicht nicht aus, da der Vektor die Zeiger speichert und nicht die tatsächlichen Objekte (es wird nicht zerstören, sondern was es enthält).

 // 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 <> erledigt die oben genannten Aufgaben für Sie. Dies bedeutet, dass der Speicher hinter den gespeicherten Zeigern freigegeben wird.

Celavek
quelle
shared_ptr ist ein intelligenter Zeiger - ein glänzender "Wrapper" für einen einfachen Zeiger, der beispielsweise einem Zeigertyp etwas KI hinzufügt. ptr_vector ist ein intelligenter Container für Zeiger - ein "Wrapper" für einen Container mit Zeigern.
Celavek
Also ist ptr_vector eine Art Ersatz für einen normalen Vektor?
Ahmed
@Ahmed Ich denke du kannst dir das so vorstellen.
Celavek
2

Durch Boost können Sie es tun>

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);

> zum Einfügen eines anderen Objekttyps in Ihren Vektorcontainer. Während Sie für den Zugriff any_cast verwenden müssen, das wie dynamic_cast funktioniert, hofft es, dass es für Ihre Anforderungen funktioniert.

user1808932
quelle
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;
}

Dies ist ein Beispiel für shared_ptr in Aktion. _obj2 wurde gelöscht, aber der Zeiger ist noch gültig. Ausgabe ist ./test _obj1: 10 _obj2: 10 _obj2: 10 erledigt

Syed Raihan
quelle
0

Der beste Weg, um verschiedene Objekte in denselben Container einzufügen, ist die Verwendung der Schleife make_shared, vector und range. Sie erhalten einen schönen, sauberen und "lesbaren" Code!

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();
Hooman
quelle