shared_ptr do tablicy: czy należy go używać?

172

Tylko małe zapytanie dotyczące shared_ptr.

Czy warto shared_ptrwskazać tablicę? Na przykład,

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

Jeśli nie, to dlaczego nie? Jednym z powodów, o których już wiem, jest to, że nie można zwiększać / zmniejszać wartości shared_ptr. Dlatego nie może być używany jako normalny wskaźnik do tablicy.

tshah06
źródło
2
FWIT, możesz również rozważyć użycie std::vector. Musisz uważać, aby przekazać tablicę przy użyciu referencji, aby nie robić jej kopii. Składnia dostępu do danych jest czystsza niż shared_ptr, a zmiana jej rozmiaru jest bardzo łatwa. I dostajesz całą dobroć STL, jeśli kiedykolwiek chcesz.
Nicu Stiurca
6
Jeśli rozmiar tablicy jest określany w czasie kompilacji, możesz również rozważyć użycie std::array. Jest prawie taka sama jak tablica surowa, ale z odpowiednią semantyką do użycia w większości składników bibliotek. Szczególnie obiekty tego typu są niszczone delete, a nie delete[]. W przeciwieństwie do tego vector, przechowuje dane bezpośrednio w obiekcie, więc nie ma dodatkowej alokacji.
celtschk

Odpowiedzi:

268

Z C ++ 17 , shared_ptrmogą być używane do zarządzania dynamicznie przydzielonego tablicy. shared_ptrSzablon argumentem w tym przypadku musi być T[N]albo T[]. Więc możesz pisać

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

Od n4659, [util.smartptr.shared.const]

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

Wymagania: Y muszą być kompletne. Wyrażenie delete[] p, kiedy Tjest typem tablicowym lub delete p, gdy Tnie jest typem tablicowym, powinno mieć dobrze zdefiniowane zachowanie i nie powinno generować wyjątków.
...
Uwagi: Gdy Tjest typem tablicowym, ten konstruktor nie będzie uczestniczył w rozpoznawaniu przeciążenia, chyba że wyrażenie delete[] pjest poprawnie sformułowane i albo Tjest U[N]i Y(*)[N]jest konwertowane na T*, albo Tjest U[]i Y(*)[]jest konwertowane na T*. ...

Aby to obsługiwać, typ pręta element_typejest teraz zdefiniowany jako

using element_type = remove_extent_t<T>;

Dostęp do elementów tablicy można uzyskać za pomocą operator[]

  element_type& operator[](ptrdiff_t i) const;

Wymaga: get() != 0 && i >= 0 . Jeśli Tjest U[N], i < N. ...
Uwagi: Gdy Tnie jest typem tablicowym, nie jest określone, czy ta funkcja składowa jest zadeklarowana. Jeśli jest zadeklarowana, nie jest określone, jaki jest jej typ zwrotu, z wyjątkiem tego, że deklaracja (choć niekoniecznie definicja) funkcji powinna być poprawnie sformułowana.


Przed C ++ 17 , shared_ptrmogłoby nie być używane do zarządzania dynamicznie przydzielone tablice. Domyślnie shared_ptrwywoła deletezarządzany obiekt, gdy nie będzie już do niego odwołań. Jednak podczas przydzielania zasobów new[]musisz zadzwonić delete[], a nie deletezwolnić zasób.

Aby poprawnie używać shared_ptrz tablicą, musisz dostarczyć niestandardowy element usuwający.

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

Utwórz shared_ptr w następujący sposób:

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

Teraz shared_ptrbędzie poprawnie dzwonić delete[]podczas niszczenia zarządzanego obiektu.

Powyższy niestandardowy usuwacz może zostać zastąpiony przez

  • std::default_deleteczęściowo kierunek typów tablicowe

    std::shared_ptr<int> sp(new int[10], std::default_delete<int[]>());
  • wyrażenie lambda

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

Ponadto, jeśli nie potrzebujesz współdzielenia zarządzanego obiektu, unique_ptrlepiej nadaje się do tego zadania, ponieważ ma częściową specjalizację dla typów tablic.

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

Zmiany wprowadzone przez rozszerzenia C ++ dotyczące podstaw biblioteki

Inną alternatywą przed C ++ 17 do tych wymienionych powyżej była specyfikacja techniczna Library Fundamentals , która została rozszerzona, shared_ptraby umożliwić jej działanie od razu po wyjęciu z pudełka w przypadkach, gdy posiada tablicę obiektów. Aktualny projekt shared_ptrzmian przewidzianych dla tego TS można znaleźć w N4082 . Te zmiany będą dostępne za pośrednictwem std::experimentalprzestrzeni nazw i zawarte w <experimental/memory>nagłówku. Oto kilka istotnych zmian dotyczących obsługi shared_ptrtablic:

- element_typeZmienia się definicja typu pręta

typedef T element_type;

 typedef typename remove_extent<T>::type element_type;

- Członek operator[]jest dodawany

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

- W przeciwieństwie do unique_ptrczęściowej specjalizacji dla tablic, oba shared_ptr<T[]>i shared_ptr<T[N]>będą prawidłowe i oba spowodują delete[]wywołanie z zarządzanej tablicy obiektów.

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

Wymagania : Ymuszą być kompletne. Wyrażenie delete[] p, kiedy Tjest typem tablicowym lub delete p, gdy Tnie jest typem tablicowym, powinno być poprawnie sformułowane, powinno mieć dobrze zdefiniowane zachowanie i nie powinno generować wyjątków. Kiedy Tjest U[N], Y(*)[N]można zamienić na T*; kiedy Tjest U[], Y(*)[]można zamienić na T*; w przeciwnym razie Y*będzie można zamienić na T*.

Pretorianin
źródło
9
+1, uwaga: są też wzmocnienia shared-array.
jogojapan
5
@ tshah06 shared_ptr::getzwraca wskaźnik do zarządzanego obiektu. Możesz więc używać go jakosp.get()[0] = 1; ... sp.get()[9] = 10;
Praetorian,
55
ALT: std::shared_ptr<int> sp( new int[10], std::default_delete<int[]>() );zobacz także en.cppreference.com/w/cpp/memory/default_delete
yohjp
2
@Jeremy Jeśli rozmiar jest znany w czasie kompilacji, nie ma potrzeby pisania dla tego klasy, std::shared_ptr<std::array<int,N>>powinno wystarczyć.
Praetorian
13
Dlaczego unique_ptruzyskuje tę częściową specjalizację, ale shared_ptrnie?
Adam
28

Prawdopodobnie łatwiejszą alternatywą, z której możesz skorzystać, jest shared_ptr<vector<int>>.

Timmmm
źródło
5
Tak to jest. Lub wektor jest nadzbiorem tablicy - ma tę samą reprezentację w pamięci (plus metadane), ale można go zmieniać. Tak naprawdę nie ma sytuacji, w których potrzebujesz tablicy, ale nie możesz użyć wektora.
Timmmm
2
Różnica polega na tym, że rozmiar wektora jest już statyczny, a dostęp do danych będzie odbywał się z podwójnym pośrednim kierunkiem. Jeśli wydajność nie jest krytycznym problemem, to działa, w przeciwnym razie udostępnianie tablicy może mieć swój własny powód.
Emilio Garavaglia
4
Wtedy prawdopodobnie możesz użyć shared_ptr<array<int, 6>>.
Timmmm
10
Inną różnicą jest to, że jest nieco większy i wolniejszy niż tablica surowa. Generalnie nie jest to problem, ale nie udawajmy, że 1 == 1.1.
Andrew,
2
Istnieją sytuacje, w których źródło danych w tablicy oznacza, że ​​konwersja do wektora jest nieporęczna lub niepotrzebna; na przykład podczas pobierania klatki z aparatu. (A przynajmniej tak rozumiem)
Narfanator