shared_ptr до масиву: чи його слід використовувати?


172

Лише невеликий запит щодо shared_ptr.

Чи корисно використовувати shared_ptrвказівку на масив? Наприклад,

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

Якщо ні, то чому б ні? Однією з причин, про яку я вже знаю, є не можна збільшувати / зменшувати shared_ptr. Отже, його не можна використовувати як звичайний вказівник на масив.


2
FWIT, ви можете також розглянути можливість простого використання std::vector. Вам доведеться бути обережними, щоб передати масив навколо, використовуючи посилання, щоб не робити його копій. Синтаксис доступу до даних чистіший, ніж до спільного_ptr, і змінити розмір дуже легко. І ви отримуєте всю доброту STL, якщо хочете.
Nicu Stiurca

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

Крім того, якщо вам дійсно не потрібне часткове управління над керованим об'єктом, a unique_ptrкраще підходить для цього завдання, оскільки він має часткову спеціалізацію для типів масивів.

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

Зміни, внесені розширеннями C ++ для основ бібліотеки

Інша альтернатива попереднього C ++ 17 перерахованій вище була надана Технічною специфікацією "Основи бібліотеки" , яка доповнила її, shared_ptrщоб дозволити їй працювати з коробки для випадків, коли їй належить масив об'єктів. Поточний проект shared_ptrзмін, передбачених для цієї ТС, можна знайти в 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.
jogojapan

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
@Jeremy Якщо розмір відомий під час компіляції, для цього немає необхідності писати клас, std::shared_ptr<std::array<int,N>>має бути достатньо.
Преторіан

13
Чому unique_ptrотримують таку часткову спеціалізацію, але shared_ptrні?
Адам

28

Можлива легша альтернатива, яку ви можете використовувати shared_ptr<vector<int>>.


5
Так. Або вектор - це супернабір масиву - він має таке ж представлення в пам'яті (плюс метадані), але може змінювати розмір. Насправді не існує жодної ситуації, коли ви хочете масив, але не можете використовувати вектор.
Timmmm

2
Різниця тут полягає в тому, що розмір вектора вже статичний, а доступ до даних буде здійснюватися з подвійним опосередкуванням. Якщо продуктивність не є критичною проблемою, це працює, інакше обмін масивом може мати свою причину.
Еміліо Гаравалья

4
Тоді ви, можливо, можете використовувати shared_ptr<array<int, 6>>.
Тімммм

10
Інша відмінність полягає в тому, що він трохи більший і повільніше, ніж необроблений масив. Як правило, це не справді, але не будемо робити вигляд, що 1 == 1,1.
Андрій

2
Бувають ситуації, коли джерело даних у масиві означає, що перетворювати на вектор небажано або непотрібно; наприклад, при отриманні камери з камери. (Або все-таки це моє розуміння)
Нарфанатор
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.