Как интеллектуальные указатели выбирают между удалением и удалением []?

Рассмотрим:

delete new std :: string [2];
delete [] new std :: string;

Всем известно, что первая ошибка. Если вторая ошибка не была, нам не нужны два разных оператора.

Теперь рассмотрим:

std :: unique_ptr <int> x (new int [2]);
std :: unique_ptr <int> y (new int);

Знает ли x использовать delete[] в отличие от delete?


Предыстория: этот вопрос всплыл у меня в голове, когда я думал, что квалификация указателей на тип массива будет удобной функцией языка.

int *[] foo = new int [2]; // OK
int *   bar = new int;     // OK
delete [] foo;             // OK
delete bar;                // OK
foo = new int;             // Compile error
bar = new int[2];          // Compile error
delete foo;                // Compile error
delete [] bar;             // Compile error

Ответ 1

К сожалению, они не знают, что удалить, поэтому они используют delete. Вот почему для каждого умного указателя у нас есть интеллектуальный массив.

std::shared_ptr uses delete
std::shared_array uses delete[]

Итак, ваша строка

std :: unique_ptr <int> x (new int [2]);

действительно вызывает поведение undefined.

Кстати, если вы пишете

std :: unique_ptr<int[]> p(new int[2]);
                     ^^

тогда delete[] будет использоваться, поскольку вы явно запросили это. Однако следующая строка все равно будет UB.

std :: unique_ptr<int[]> p(new int);

Причина, по которой они не могут выбирать между delete и delete[], состоит в том, что new int и new int[2] имеют точно такой же тип - int*.

Здесь связанный вопрос об использовании правильных удалений в случае smart_ptr<void> и smart_ptr<Base>, когда Base не имеет виртуального деструктора.

Ответ 2

Нет никакого "магического" способа определить, относится ли int* к:

  • одно целое, выделенное кучей
  • выделенный массив с кучей
  • целое число в массиве, выделенном кучей

Информация была потеряна системой типов, и никакой способ выполнения (переносной) не смог ее исправить. Это бесит и серьезный дефект дизайна (*) в C, который С++ унаследовал (ради совместимости, некоторые говорят).

Однако существуют некоторые способы работы с массивами в интеллектуальных указателях.

Во-первых, ваш тип unique_ptr неверен для работы с массивом, вы должны использовать:

std::unique_ptr<int[]> p(new int[10]);

который предназначен для вызова delete[]. Я знаю, что есть разговоры о внедрении определенного предупреждения в Clang, чтобы выявить явные несоответствия с помощью unique_ptr: это проблема качества реализации (стандарт просто говорит, что это UB), и не все случаи могут быть охвачены без WPA.

Во-вторых, a boost::shared_ptr может иметь пользовательский отладчик, который может, если вы его спроектируете, чтобы вызвать правильный оператор delete[]. Однако для этого существует boost::shared_array. Еще раз, обнаружение несоответствий - это проблема качества реализации. std::shared_ptr испытывает ту же проблему (отредактирован после замечания ildjarn).

Я согласен, что это некрасиво. Кажется настолько неприятным, что недостаток дизайна (*) от истоков C преследует нас сегодня.

(*) некоторые скажут, что C сильно склоняется к тому, чтобы избежать накладных расходов, и это добавило бы накладные расходы. Я частично не согласен: malloc всегда знает размер блока, в конце концов.

Ответ 3

От Документация по Microsoft:

(Частичная специализация unique_ptr<Type[]> управляет объектами массива, выделенными new[], и имеет дефолт по умолчанию default_delete<Type[]>, специализированный для вызова delete[] _Ptr.)

Я добавил два финальных квадратных скобки, похоже на опечатку, поскольку без них они не имеют смысла.

Ответ 4

std::unique_ptr не предназначен для массива, поскольку я цитирую последний форматирующий документ:

Обычно a shared_ptr не может правильно удерживать указатель на динамически распределенный массив. См. shared_array для этого использования.

Если вы хотите управлять памятью для массива указателей, у вас есть несколько вариантов, которые зависят от вашего требования:

  • Используйте boost::shared_array
  • Используйте std::vector для boost::shared_ptr
  • Используйте контейнер указателя boost, например boost::ptr_vector