Зачем нам вообще нужен оператор «delete []»? - PullRequest
31 голосов
/ 31 октября 2008

Это вопрос, который мучил меня некоторое время. Я всегда думал, что C ++ должен быть спроектирован так, чтобы оператор «delete» (без скобок) работал даже с оператором «new []».

На мой взгляд, пишу это:

int* p = new int;

должно быть эквивалентно выделению массива из 1 элемента:

int* p = new int[1];

Если бы это было так, оператор «delete» мог всегда удалять массивы, и нам не понадобился бы оператор «delete []».

Есть ли причина, по которой оператор "delete []" был введен в C ++? Единственная причина, по которой я могу придумать, состоит в том, что выделение массивов занимает небольшой объем памяти (вы должны где-то хранить размер массива), поэтому различие между «delete» и «delete []» было небольшой оптимизацией памяти.

Ответы [ 7 ]

37 голосов
/ 31 октября 2008

Это так, что деструкторы отдельных элементов будут называться. Да, для массивов POD нет большой разницы, но в C ++ вы можете иметь массивы объектов с нетривиальными деструкторами.

Теперь ваш вопрос: почему бы не заставить new и delete вести себя как new[] и delete[] и избавиться от new[] и delete[]? Я бы вернулся к книге Страуструпа «Дизайн и эволюция», где он сказал, что если вы не используете функции C ++, вам не нужно платить за них (по крайней мере, во время выполнения). Как сейчас, new или delete будут вести себя так же эффективно, как malloc и free. Если бы delete имел значение delete[], во время выполнения были бы некоторые дополнительные издержки (как указал Джеймс Керран).

9 голосов
/ 31 октября 2008

Черт, я пропустил весь вопрос, но я оставлю свой первоначальный ответ в виде sidenote. Почему мы удалили [], потому что давным-давно мы удалили [cnt], даже сегодня, если вы пишете delete [9] или delete [cnt], компилятор просто игнорирует то, что находится между [], но компилируется нормально. В то время C ++ сначала обрабатывался внешним интерфейсом, а затем передавался обычному компилятору C. Они не могли хранить трюк где-то под занавесом, может быть, они даже не могли думать об этом в то время. А для обратной совместимости, компиляторы, скорее всего, использовали значение, указанное между [], в качестве счетчика массива, если такого значения нет, то они получают счетчик из префикса, поэтому он работал в обоих направлениях. Позже мы ничего не печатали между [] и все работало. Сегодня я не думаю, что «delete []» необходимо, но реализации требуют этого.

Мой оригинальный ответ (который не соответствует сути) ::

«удалить» удаляет один объект. «delete []» удаляет массив объектов. Чтобы delete [] работал, реализация хранит количество элементов в массиве. Я просто дважды проверил это, отлаживая код ASM. В проверенной реализации (VS2005) счет был сохранен как префикс к массиву объектов.

Если вы используете «delete []» для одного объекта, переменная count является мусором, поэтому код падает. Если вы используете «delete» для массива объектов из-за некоторой несогласованности, код падает. Я проверял эти случаи только сейчас!

"delete просто удаляет память, выделенную для массива." Утверждение в другом ответе не правильно. Если объект является классом, delete вызовет DTOR. Просто поместите точку останова в код DTOR и удалите объект, точка останова попадет.

Что мне пришло в голову, так это то, что если бы компилятор и библиотеки предполагали, что все объекты, выделенные «new», являются объектными массивами, было бы нормально вызвать «delete» для отдельных объектов или массивов объектов. Отдельные объекты просто были бы особым случаем массива объектов, имеющего счет 1. Может быть, что-то мне не хватает, в любом случае ...

6 голосов
/ 31 октября 2008

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

Единственное, о чем я могу подумать, это то, что есть небольшая дополнительная нагрузка для обработки одного объекта как массива (ненужный "for(int i=0; i<1; ++i)")

5 голосов
/ 31 октября 2008

Добавление этого, поскольку ни один другой ответ в настоящее время не обращается к нему:

Массив 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 ++.

3 голосов
/ 31 октября 2008

У Маршалла Клайна есть информация по этой теме .

2 голосов
/ 31 октября 2008

delete [] гарантирует, что деструктор каждого члена вызывается (если применимо к типу), тогда как delete просто удаляет память, выделенную для массива.

Вот хорошее чтение: http://www.informit.com/guides/content.aspx?g=cplusplus&seqNum=287

И нет, размеры массивов нигде не хранятся в C ++. (Спасибо всем за то, что указали на то, что это утверждение неверно.)

0 голосов
/ 04 декабря 2013

Я немного смущен ответом Аарона и, честно говоря, признаюсь, что не до конца понимаю, зачем и где необходимо удалить [].

Я провел несколько экспериментов с его примером кода (после исправления нескольких опечаток). Вот мои результаты. Проверь: ~ База нуждалась в теле функции База * 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 работает в первом примере; Я только знаю, что это не выдает сообщение об ошибке компилятора.

...