Зачем нам вообще нужен оператор “delete[]”?
-
05-07-2019 - |
Вопрос
Этот вопрос не дает мне покоя уже некоторое время.Я всегда думал, что C ++ должен был быть спроектирован так, чтобы оператор "delete" (без скобок) работал даже с оператором "new[]".
На мой взгляд, написание этого:
int* p = new int;
должно быть эквивалентно выделению массива из 1 элемента:
int* p = new int[1];
Если бы это было правдой, оператор "delete" всегда мог бы удалять массивы, и нам не понадобился бы оператор "delete[]".
Есть ли какая-либо причина, по которой оператор "delete[]" был введен в C ++?Единственная причина, о которой я могу думать, заключается в том, что выделение массивов занимает небольшой объем памяти (вы должны где-то хранить размер массива), так что различие "delete" и "delete []" было небольшой оптимизацией памяти.
Решение
Это так, что деструкторы отдельных элементов будут вызваны. Да, для массивов POD нет большой разницы, но в C ++ вы можете иметь массивы объектов с нетривиальными деструкторами.
Теперь ваш вопрос: почему бы не заставить new
и delete
вести себя как new []
и delete []
и избавиться от new []
и delete []
? Я бы хотел вернуться к «Дизайну и эволюции» Страуструпа. книга, в которой он сказал, что если вы не используете функции C ++, вам не нужно платить за них (по крайней мере, во время выполнения). Теперь, new
или delete
будет работать так же эффективно, как malloc
и free
. Если бы delete
имел значение delete []
, во время выполнения были бы некоторые дополнительные издержки (как указал Джеймс Керран).
Другие советы
Черт, я пропустил всю суть вопроса, но я оставлю свой первоначальный ответ в качестве примечания.Почему у нас есть delete [], потому что давным-давно у нас был delete [cnt], даже сегодня, если вы пишете delete [9] или delete[cnt], компилятор просто игнорирует то, что находится между [], но компилируется нормально.В то время C ++ сначала обрабатывался внешним интерфейсом, а затем передавался обычному компилятору C.Они не могли проделать трюк с хранением графы где-нибудь под занавеской, возможно, они даже не могли подумать об этом в то время.И для обратной совместимости компиляторы, скорее всего, использовали значение, указанное между [], в качестве count массива, если такого значения нет, то они получали count из префикса, так что это работало в обоих направлениях.Позже мы ничего не набрали между [], и все заработало.Сегодня я не думаю, что "удалить []" необходимо, но реализации требуют именно этого.
Мой первоначальный ответ (который упускает суть) ::
"удалить" удаляет один объект."удалить[]" удаляет массив объектов.Чтобы delete[] работал, реализация сохраняет количество элементов в массиве.Я только что перепроверил это, отлаживая ASM-код.В реализации (VS2005), которую я тестировал, счетчик был сохранен как префикс к массиву объектов.
Если вы используете "delete[]" для одного объекта, переменная count является мусорной, поэтому код выходит из строя.Если вы используете "удалить" для массива объектов, из-за некоторой несогласованности код завершает работу с ошибкой.Я только что проверил эти случаи !
"удалить просто удаляет память, выделенную для массива". утверждение в другом ответе неверно.Если объект является классом, delete вызовет DTOR.Просто поместите точку останова в код DTOR и удалите объект, точка останова будет достигнута.
Мне пришло в голову, что, если компилятор и библиотеки предположили, что все объекты, выделенные с помощью "new", являются массивами объектов, было бы нормально вызвать "delete" для отдельных объектов или массивов объектов.Отдельные объекты просто были бы частным случаем массива объектов, имеющего количество, равное 1.Может быть, я все равно чего-то не понимаю...
Поскольку все остальные, похоже, не поняли смысл вашего вопроса, я просто добавлю, что несколько лет назад у меня была такая же мысль, и я так и не смог получить ответ.
Единственное, о чем я могу подумать, это то, что есть небольшая дополнительная нагрузка для обработки одного объекта как массива (ненужный " для (int i = 0; i < 1; ++ i) )
")
Добавление этого, поскольку в настоящее время нет другого ответа на него:
Массив delete []
нельзя использовать для указателя на базовый класс - когда компилятор сохраняет количество объектов при вызове new []
, он не хранит типы или размеры объектов (как отметил Дэвид, в C ++ вы редко платите за функцию, которой не пользуетесь). Однако скалярное delete
можно безопасно удалить через базовый класс, поэтому оно используется как для обычной очистки объекта, так и для полиморфной очистки:
struct Base { virtual ~Base(); };
struct Derived : Base { };
int main(){
Base* b = new Derived;
delete b; // this is good
Base* b = new Derived[2];
delete[] b; // bad! undefined behavior
}
Однако, в противоположном случае - не виртуальный деструктор - скалярный delete
должен быть как можно дешевле - он не должен проверять ни количество объектов, ни тип объекта. удален. Это делает удаление для встроенного типа или простого старого типа данных очень дешевым, так как компилятору нужно только вызывать :: operator delete
и ничего больше:
int main(){
int * p = new int;
delete p; // cheap operation, no dynamic dispatch, no conditional branching
}
Хотя это не исчерпывающий подход к распределению памяти, я надеюсь, что это поможет прояснить широту вариантов управления памятью, доступных в C ++.
Маршалл Клайн имеет некоторую информацию по этой теме а>. р>
delete []
гарантирует, что деструктор каждого члена вызывается (если применимо к типу), а delete
просто удаляет память, выделенную для массива.
Вот хорошее прочтение: http: //www.informit. ком / направляющие / content.aspx г = cplusplus & амп;? SEQNUM = 287 р>
И нет, размеры массивов нигде не хранятся в C ++. (Спасибо всем за то, что указали, что это утверждение неверно.)
Я немного сбит с толку ответом Аарона и, честно говоря, признаю, что не совсем понимаю, зачем и где нужно delete [].
Я провел несколько экспериментов с его образцом кода (после исправления нескольких опечаток).Вот мои результаты.Опечатки:~Base требовалось тело функции Base *b был объявлен дважды
struct Base { virtual ~Base(){ }>; };
struct Derived : Base { };
int main(){
Base* b = new Derived;
delete b; // this is good
<strike>Base</strike> b = new Derived[2];
delete[] b; // bad! undefined behavior
}
Компиляция и выполнение
david@Godel:g++ -o atest atest.cpp
david@Godel: ./atest
david@Godel: # No error message
Измененная программа с удалением[] удалена
struct Base { virtual ~Base(){}; };
struct Derived : Base { };
int main(){
Base* b = new Derived;
delete b; // this is good
b = new Derived[2];
delete b; // bad! undefined behavior
}
Компиляция и выполнение
david@Godel:g++ -o atest atest.cpp
david@Godel: ./atest
atest(30746) malloc: *** error for object 0x1099008c8: pointer being freed was n
ot allocated
*** set a breakpoint in malloc_error_break to debug
Abort trap: 6
Конечно, я не знаю, действительно ли delete[] b работает в первом примере;Я только знаю, что это не выдает сообщение об ошибке компилятора.