Как интеллектуальные указатели выбирают между удалением и удалением []?
Рассмотрим:
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