shared_ptr для массива: его следует использовать?

172

Просто небольшой вопрос по поводу shared_ptr.

Это хорошая практика, чтобы использовать shared_ptrуказание на массив? Например,

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

Если нет, то почему? Одна причина, о которой я уже знаю, это то, что нельзя увеличивать / уменьшать shared_ptr. Следовательно, его нельзя использовать как обычный указатель на массив.

tshah06
источник
2
FWIT, вы также можете рассмотреть возможность использования std::vector. Вы должны быть осторожны, чтобы передать массив, используя ссылки, чтобы вы не делали его копии. Синтаксис для доступа к данным чище, чем shared_ptr, и изменить его размер очень легко. И вы получите все добро STL, если захотите.
Нику Стирка
6
Если размер массива определяется во время компиляции, вы можете также рассмотреть возможность использования std::array. Он почти такой же, как необработанный массив, но с правильной семантикой для использования в большинстве библиотечных компонентов. Особенно объекты такого типа уничтожаются delete, а не delete[]. И, в отличие от этого vector, он хранит данные непосредственно в объекте, поэтому вы не получаете дополнительного распределения.
celtschk

Ответы:

268

С C ++ 17 , shared_ptrможет использоваться для управления динамически размещенным массивом. shared_ptrАргумент шаблона в этом случае должен быть T[N]или T[]. Так что вы можете написать

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

От n4659 [util.smartptr.shared.const]

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

Требуется: Y должен быть полный тип. Выражение delete[] p, когда Tявляется типом массива, или delete p, когда Tне является типом массива, должно иметь четко определенное поведение и не должно генерировать исключения.
...
Примечания: Когда Tтип массива, этот конструктор не должен участвовать в разрешении перегрузки, если выражение не delete[] pявляется правильно сформированным и либо Tявляется U[N]и Y(*)[N]может быть преобразовано T*, либо Tявляется U[]и Y(*)[]может быть преобразовано в T*. ...

Чтобы поддержать это, тип члена element_typeтеперь определен как

using element_type = remove_extent_t<T>;

Доступ к элементам массива можно получить с помощью operator[]

  element_type& operator[](ptrdiff_t i) const;

Требуется: get() != 0 && i >= 0 . Если Tесть U[N], i < N. ...
Примечания: Когда Tтип не является массивом, неизвестно, объявлена ​​ли эта функция-член. Если он объявлен, он не определен, каков его тип возврата, за исключением того, что объявление (хотя и не обязательно определение) функции должно быть правильно сформировано.


До C ++ 17 , shared_ptrможет не использоваться для управления динамически распределяемых массивов. По умолчанию shared_ptrвызовет deleteуправляемый объект, когда на него больше не будет ссылок. Однако, когда вы распределяете использование, new[]вам нужно звонить delete[], а не deleteосвобождать ресурс.

Для правильного использования shared_ptrс массивом вы должны предоставить пользовательское средство удаления.

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

Создайте shared_ptr следующим образом:

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

Теперь shared_ptrбудет правильно звонить delete[]при уничтожении управляемого объекта.

Пользовательский удалитель выше может быть заменен

  • std::default_deleteчастичная специализация для типов массивов

    std::shared_ptr<int> sp(new int[10], std::default_delete<int[]>());
  • лямбда-выражение

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

Кроме того, если вам на самом деле не нужен общий доступ к управляемому объекту, unique_ptrлучше подходит для этой задачи, поскольку он имеет частичную специализацию для типов массивов.

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

Изменения, внесенные расширениями C ++ для основ библиотеки

Еще одна альтернатива до C ++ 17, перечисленная выше, была предоставлена Технической спецификацией Основы библиотеки , которая дополнила shared_ptrего, чтобы он мог работать «из коробки» для случаев, когда он владеет массивом объектов. Текущий проект shared_ptrизменений, намеченных для этого TS, может быть найден в N4082 . Эти изменения будут доступны через std::experimentalпространство имен и включены в <experimental/memory>заголовок. Вот некоторые из важных изменений в поддержке shared_ptrмассивов:

- Определение element_typeизменения типа члена

typedef T element_type;

 typedef typename remove_extent<T>::type element_type;

- Член operator[]добавляется

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

- В отличие от unique_ptrчастичной специализации для массивов, оба shared_ptr<T[]>и shared_ptr<T[N]>будут действительными, и оба приведут к delete[]вызову в управляемом массиве объектов.

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

Требуется : Yдолжен быть полный тип. Выражение delete[] p, когда Tявляется типом массива, или delete p, когда Tэто не тип массива, должно быть правильно сформированным, должно иметь четко определенное поведение и не должно генерировать исключения. Когда Tесть U[N], Y(*)[N]должен быть конвертируемым в T*; когда Tесть U[], Y(*)[]должен быть конвертируемым в T*; в противном случае, Y*должен быть конвертируемым в T*.

преторианец
источник
9
+1, замечание: есть и Boost's shared-array.
Джогоджапан
5
@ tshah06 shared_ptr::getвозвращает указатель на управляемый объект. Таким образом, вы можете использовать его какsp.get()[0] = 1; ... sp.get()[9] = 10;
преторианский
55
ALT: std::shared_ptr<int> sp( new int[10], std::default_delete<int[]>() );см. Также en.cppreference.com/w/cpp/memory/default_delete
yohjp
2
@ Джереми Если размер известен во время компиляции, нет необходимости писать класс для этого, std::shared_ptr<std::array<int,N>>должно быть достаточно.
преторианец
13
Почему unique_ptrполучается эта частичная специализация, но shared_ptrнет?
Адам
28

Возможно, более простая альтернатива, которую вы могли бы использовать shared_ptr<vector<int>>.

Timmmm
источник
5
Да, это так. Или вектор является надмножеством массива - он имеет такое же представление в памяти (плюс метаданные), но с изменяемым размером. На самом деле не существует ситуаций, когда вы хотите получить массив, но не можете использовать вектор.
Тимммм
2
Разница здесь в том, что размер вектора больше не является статическим, и доступ к данным будет осуществляться с двойной косвенностью. Если производительность не является критической проблемой, это работает, иначе совместное использование массива может иметь свою собственную причину.
Эмилио Гаравалья
4
Тогда вы, вероятно, можете использовать shared_ptr<array<int, 6>>.
Тимммм
10
Другое отличие состоит в том, что он немного больше и медленнее, чем необработанный массив. Обычно это не проблема, но давайте не будем притворяться, что 1 == 1.1.
Андрей
2
Существуют ситуации, когда источник данных в массиве означает, что преобразование в вектор громоздко или ненужно; например, при получении кадра с камеры. (Или, в любом случае, это мое понимание)
Нарфанатор