shared_ptr zu einem Array: sollte es verwendet werden?

172

Nur eine kleine Frage zu shared_ptr.

Ist es eine gute Praxis, shared_ptrauf ein Array zu zeigen? Beispielsweise,

shared_ptr<int> sp(new int[10]);

Wenn nicht, warum dann nicht? Ein Grund, den ich bereits kenne, ist, dass man das nicht erhöhen / verringern kann shared_ptr. Daher kann es nicht wie ein normaler Zeiger auf ein Array verwendet werden.

tshah06
quelle
2
FWIT können Sie auch in Betracht ziehen, nur zu verwenden std::vector. Sie müssen vorsichtig sein, um das Array mithilfe von Referenzen weiterzugeben, damit Sie keine Kopien davon erstellen. Die Syntax für den Zugriff auf Daten ist sauberer als shared_ptr, und die Größenänderung ist sehr, sehr einfach. Und Sie erhalten alle STL-Güte, falls Sie es jemals wollen.
Nicu Stiurca
6
Wenn die Größe des Arrays zur Kompilierungszeit festgelegt wird, können Sie auch die Verwendung in Betracht ziehen std::array. Es ist fast das gleiche wie ein Raw-Array, jedoch mit der richtigen Semantik für die meisten Bibliothekskomponenten. Insbesondere Objekte dieser Art werden mit deletenicht zerstört delete[]. Im Gegensatz vectordazu werden die Daten direkt im Objekt gespeichert, sodass Sie keine zusätzliche Zuordnung erhalten.
Celtschk

Antworten:

268

Mit C ++ 17 , shared_ptrkann verwendet werden , um eine dynamisch zugewiesenen Array zu verwalten. Das shared_ptrVorlagenargument muss in diesem Fall T[N]oder sein T[]. Also kannst du schreiben

shared_ptr<int[]> sp(new int[10]);

Ab n4659 [util.smartptr.shared.const]

  template<class Y> explicit shared_ptr(Y* p);

Benötigt: Y muss ein vollständiger Typ sein. Der Ausdruck delete[] p, wenn Tes sich um einen Array-Typ handelt oder delete pwenn Tes sich nicht um einen Array-Typ handelt, muss ein genau definiertes Verhalten aufweisen und keine Ausnahmen auslösen.
...
Anmerkungen: Wenn Tes sich um einen Array-Typ handelt, darf dieser Konstruktor nicht an der Überladungsauflösung teilnehmen, es sei denn, der Ausdruck delete[] pist wohlgeformt und Tist U[N]und Y(*)[N]ist konvertierbar in T*oder Tist U[]und Y(*)[]ist konvertierbar in T*. ...

Um dies zu unterstützen, ist der Elementtyp element_typejetzt definiert als

using element_type = remove_extent_t<T>;

Auf Array-Elemente kann mit zugegriffen werden operator[]

  element_type& operator[](ptrdiff_t i) const;

Benötigt : get() != 0 && i >= 0 . Wenn Tja U[N], i < N. ...
Anmerkungen: Wenn Tes sich nicht um einen Array-Typ handelt, ist nicht angegeben, ob diese Elementfunktion deklariert ist. Wenn es deklariert ist, ist nicht angegeben, um welchen Rückgabetyp es sich handelt, mit der Ausnahme, dass die Deklaration (obwohl nicht unbedingt die Definition) der Funktion wohlgeformt sein muss.


Vor dem C ++ 17 , shared_ptrkonnte nicht dynamisch zugewiesenen Arrays verwalten verwendet werden. Standardmäßig shared_ptrwird deletedas verwaltete Objekt aufgerufen , wenn keine Verweise mehr darauf vorhanden sind. Wenn Sie jedoch mit zuweisen, müssen new[]Sie die Ressource aufrufen delete[]und nicht delete, um sie freizugeben.

Zur korrekten Verwendung shared_ptrmit einem Array müssen Sie einen benutzerdefinierten Löscher angeben.

template< typename T >
struct array_deleter
{
  void operator ()( T const * p)
  { 
    delete[] p; 
  }
};

Erstellen Sie den shared_ptr wie folgt:

std::shared_ptr<int> sp(new int[10], array_deleter<int>());

shared_ptrWird jetzt korrekt aufgerufen, delete[]wenn das verwaltete Objekt zerstört wird.

Der oben angegebene benutzerdefinierte Löscher kann durch ersetzt werden

  • die std::default_deleteteilweise Spezialisierung für Array-Typen

    std::shared_ptr<int> sp(new int[10], std::default_delete<int[]>());
  • ein Lambda-Ausdruck

    std::shared_ptr<int> sp(new int[10], [](int *p) { delete[] p; });

Außerdem unique_ptrist a für diese Aufgabe besser geeignet , es sei denn, Sie benötigen tatsächlich eine gemeinsame Verwaltung des verwalteten Objekts , da es eine teilweise Spezialisierung für Array-Typen aufweist.

std::unique_ptr<int[]> up(new int[10]); // this will correctly call delete[]

Änderungen, die durch die C ++ - Erweiterungen für Bibliotheksgrundlagen eingeführt wurden

Eine weitere Alternative vor C ++ 17 zu den oben aufgeführten wurde von der Library Fundamentals Technical Specification bereitgestellt , die erweitert wurde shared_ptr, damit sie für den Fall, dass sie ein Array von Objekten besitzt, sofort einsatzbereit ist. Der aktuelle Entwurf der shared_ptrfür diesen TS geplanten Änderungen ist in N4082 zu finden . Auf diese Änderungen kann über den std::experimentalNamespace zugegriffen und in die <experimental/memory>Kopfzeile aufgenommen werden. Einige der relevanten Änderungen zur Unterstützung shared_ptrvon Arrays sind:

- Die Definition des Elementtyps element_typeändert sich

typedef T element_type;

 typedef typename remove_extent<T>::type element_type;

- Mitglied operator[]wird hinzugefügt

 element_type& operator[](ptrdiff_t i) const noexcept;

- Im Gegensatz zur unique_ptrteilweisen Spezialisierung für Arrays sind beide shared_ptr<T[]>und shared_ptr<T[N]>gültig und führen dazu, delete[]dass das verwaltete Array von Objekten aufgerufen wird.

 template<class Y> explicit shared_ptr(Y* p);

Benötigt : Ymuss ein vollständiger Typ sein. Der Ausdruck delete[] p, wenn Tes sich um einen Array-Typ handelt oder delete pwenn Tes sich nicht um einen Array-Typ handelt, muss wohlgeformt sein, ein genau definiertes Verhalten aufweisen und keine Ausnahmen auslösen. Wann Tist U[N], Y(*)[N]soll konvertierbar sein zu T*; wann Tist U[], Y(*)[]soll konvertierbar sein zu T*; Andernfalls Y*ist konvertierbar in T*.

Prätorianer
quelle
9
+1, Bemerkung: Es gibt auch Boost's shared-array.
Jogojapan
5
@ tshah06 shared_ptr::getgibt einen Zeiger auf das verwaltete Objekt zurück. So können Sie es alssp.get()[0] = 1; ... sp.get()[9] = 10;
Prätorianer
55
ALT: std::shared_ptr<int> sp( new int[10], std::default_delete<int[]>() );siehe auch en.cppreference.com/w/cpp/memory/default_delete
yohjp
2
@Jeremy Wenn die Größe zur Kompilierungszeit bekannt ist, muss dafür keine Klasse geschrieben werden, std::shared_ptr<std::array<int,N>>sollte ausreichen.
Prätorianer
13
Warum unique_ptrbekommt diese Teilspezialisierung aber shared_ptrnicht?
Adam
28

Eine möglicherweise einfachere Alternative, die Sie möglicherweise verwenden können, ist shared_ptr<vector<int>>.

Timmmm
quelle
5
Ja, so ist es. Oder ein Vektor ist eine Obermenge eines Arrays - er hat dieselbe speicherinterne Darstellung (plus Metadaten), kann jedoch in der Größe geändert werden. Es gibt eigentlich keine Situationen, in denen Sie ein Array möchten, aber keinen Vektor verwenden können.
Timmmm
2
Der Unterschied besteht hier darin, dass die Vektorgröße nicht mehr statisch ist und der Zugriff auf die Daten mit einer doppelten Indirektion erfolgt. Wenn die Leistung nicht das kritische Problem ist, funktioniert dies. Andernfalls kann die Freigabe eines Arrays einen eigenen Grund haben.
Emilio Garavaglia
4
Dann können Sie wahrscheinlich verwenden shared_ptr<array<int, 6>>.
Timmmm
10
Der andere Unterschied ist, dass es etwas größer und langsamer als ein Raw-Array ist. Im Allgemeinen kein wirkliches Problem, aber tun wir nicht so, als wäre 1 == 1.1.
Andrew
2
Es gibt Situationen, in denen die Quelle der Daten im Array bedeutet, dass die Konvertierung in einen Vektor unhandlich oder unnötig ist. Zum Beispiel, wenn Sie ein Bild von einer Kamera erhalten. (Oder das ist sowieso mein Verständnis)
Narfanator