Я начну путем высказывания, использовать интеллектуальные указатели, и Вы никогда не должны будете волноваться об этом.
Каковы проблемы со следующим кодом?
Foo * p = new Foo;
// (use p)
delete p;
p = NULL;
Это было зажжено ответом и комментариями к другому вопросу. Один комментарий от Neil Butterworth генерировал несколько upvotes:
Установка в NULL следующего указателей удаляет, не универсальная хорошая практика в C++. Существуют времена, когда хорошо, чтобы сделать, и времена, когда это бессмысленно и может скрыть ошибки.
Существует много обстоятельств, где это не помогло бы. Но по моему опыту, это не может причинить боль. Кто-то просвещает меня.
Установка указателя на 0 (который является "нулевым" в стандартном C ++, определение NULL из C несколько отличается) позволяет избежать сбоев при двойном удалении.
Примите во внимание следующее:
Foo* foo = 0; // Sets the pointer to 0 (C++ NULL)
delete foo; // Won't do anything
Принимая во внимание:
Foo* foo = new Foo();
delete foo; // Deletes the object
delete foo; // Undefined behavior
Другими словами, если вы не установите удаленные указатели на 0, у вас возникнут проблемы, если вы выполняете двойное удаление. Аргументом против установки указателей на 0 после удаления может быть то, что это просто маскирует ошибки двойного удаления и оставляет их необработанными.
Очевидно, лучше не иметь ошибок двойного удаления, но в зависимости от семантики владения и жизненных циклов объекта это может быть трудно достичь на практике. Я предпочитаю маскированную ошибку двойного удаления, а не UB.
Наконец, примечание относительно управления распределением объектов, я предлагаю вам взглянуть на std :: unique_ptr
для строгого / единственного владения, std: :
Если код не относится к наиболее критичной для производительности части ваше приложение, сделайте его простым и используйте shared_ptr:
shared_ptr<Foo> p(new Foo);
//No more need to call delete
Оно выполняет подсчет ссылок и является потокобезопасным. Вы можете найти его в tr1 (пространство имен std :: tr1, #include
Всегда есть висячие указатели , о которых нужно беспокоиться.
Если вы собираетесь перераспределить указатель перед его повторным использованием (разыменование его, передача его функции и т. Д.), Преобразование указателя в NULL - это просто дополнительная операция. Однако, если вы не уверены, будет ли он перераспределен перед повторным использованием, хорошей идеей будет установка его в NULL.
Как многие говорили, конечно, намного проще просто использовать умные указатели.
Изменить: Как сказал Томас Мэтьюз в в этом более раннем ответе , если указатель удален в деструкторе, нет необходимости назначать ему NULL, поскольку он не будет использоваться снова, потому что объект уже уничтожается.
Да.
Единственный «вред», который он может нанести, - это неэффективность (ненужная операция сохранения) в вашем программа - но эти накладные расходы будут незначительны по сравнению с затратами на выделение и освобождение блока памяти в большинстве случаев.
Если вы этого не сделаете, вы получите неприятные ошибки разыменования указателя однажды.
Я всегда использую макрос для удаления:
#define SAFEDELETE(ptr) { delete(ptr); ptr = NULL; }
(и аналогичный для массива, free (), освобождая дескрипторы)
Вы также можете написать методы «самоудаления», которые принимают ссылку на вызывающий указатель кода, поэтому они принудительно устанавливают указатель вызывающего кода на NULL. Например, чтобы удалить поддерево многих объектов:
static void TreeItem::DeleteSubtree(TreeItem *&rootObject)
{
if (rootObject == NULL)
return;
rootObject->UnlinkFromParent();
for (int i = 0; i < numChildren)
DeleteSubtree(rootObject->child[i]);
delete rootObject;
rootObject = NULL;
}
edit
Да, эти методы действительно нарушают некоторые правила использования макросов (и да, в наши дни вы, вероятно, могли бы достичь того же результата с помощью шаблонов) - но используя на протяжении многих лет я никогда никогда не имел доступа к мертвой памяти - одна из самых неприятных, наиболее сложных и требующих много времени проблем для отладки, с которыми вы можете столкнуться. На практике за многие годы они эффективно устранили целый класс ошибок из каждой команды, в которой я их представил.
Есть также много способов реализовать вышеизложенное - я просто пытаюсь проиллюстрировать идею принуждения людей к NULL-указателю, если они удаляют объект, вместо того, чтобы предоставить им средство освободить память, которая не имеет NULL указатель звонящего.
Конечно, приведенный выше пример - это всего лишь шаг к автоматическому указателю. Я этого не предлагал, потому что ОП специально спрашивал о случае неиспользования автоматического указателя.
Я могу представить, что установка указателя на NULL после его удаления будет полезна в редких случаях, когда существует допустимый сценарий повторного использования его в одной функции (или объекте). В противном случае это не имеет смысла - указатель должен указывать на что-то значимое, пока оно существует - точка.
Позвольте мне расширить то, что вы уже добавили в свой вопрос.
Вот что вы добавили в свой вопрос в виде маркированного списка:
Установка указателей на NULL после delete не является универсальной хорошей практикой в C ++. Бывают случаи, когда:
Однако нет случаев , когда это плохо ! Вы не добавите больше ошибок, явно обнуляя его, вы не утечки памяти, вы не вызовете неопределенное поведение .
Итак, если если есть сомнения, просто обнуляйте его.
Сказав это, если вы чувствуете, что вам нужно явно обнулить некоторый указатель, то для меня это звучит так, как будто вы недостаточно разделили метод,
Я немного изменю ваш вопрос:
Вы бы использовали неинициализированный указатель? Вы знаете, что вы не сделали установите значение NULL или выделите ему память указывает на?
Существует два сценария, в которых установка указателя на NULL может быть пропущена:
Между тем, утверждение о том, что установка указателя на NULL может скрыть ошибки, для меня звучит как аргумент, что вы не должны исправлять ошибку, потому что исправление может скрыть другую ошибку. Единственные ошибки, которые могут появиться, если указатель не установлен в NULL, - это ошибки, которые пытаются использовать указатель. Но установка значения NULL на самом деле вызовет ту же ошибку, что и при использовании с освобожденной памятью, не так ли?
Как говорили другие, удалить ptr; ptr = 0;
не заставит демонов вылететь из вашего носа. Тем не менее, он поощряет использование ptr
как своего рода флага. Код засоряется delete
и установкой указателя на NULL
. Следующий шаг - разбросать if (arg == NULL) return;
по вашему коду, чтобы защитить от случайного использования указателя NULL
. Проблема возникает, когда проверки на NULL
становятся вашим основным средством проверки состояния объекта или программы.
Я уверен, что есть запах кода, связанный с использованием указателя в качестве флага где-то но я не нашел ни одного.
Если после удалить
есть еще код, то да. Когда указатель удаляется в конструкторе или в конце метода или функции, нет.
Смысл этой притчи состоит в том, чтобы напомнить программисту во время выполнения, что объект уже был удален.
Еще лучшая практика - использовать интеллектуальные указатели (общие или ограниченные), которые автоматически удаляют свои целевые объекты.
Во-первых, есть много существующих вопросов по этой и тесно связанным темам, например Почему не удалить, установить указатель на NULL? .
В ваш код, проблема в том, что происходит (используйте p). Например, если где-то у вас есть такой код:
Foo * p2 = p;
, то установка p на NULL очень мало дает, так как у вас все еще есть указатель p2, о котором нужно беспокоиться.
Это не значит, что установка указателя на NULL всегда бессмысленно. Например, если p было переменной-членом, указывающей на ресурс, время жизни которого не было точно таким же, как у класса, содержащего p, то установка p на NULL могла бы быть полезным способом указать наличие или отсутствие ресурса.
У меня есть еще лучшая передовая практика: по возможности закройте область видимости переменной!
{
Foo* pFoo = new Foo;
// use pFoo
delete pFoo;
}
Если у вас нет другого ограничения, которое заставляет вас либо устанавливать, либо не устанавливать указатель в NULL после его удаления (одно такое ограничение было упомянуто Нилом Баттервортом ), тогда я лично предпочитаю оставить все как есть.
Для меня вопрос не в том, "хорошая ли это идея?" но «какое поведение я бы предотвратил или позволил бы добиться успеха, сделав это?» Например, если это позволяет другому коду увидеть, что указатель больше не доступен, почему другой код даже пытается просмотреть освобожденные указатели после того, как они освобождены? Обычно это ошибка.
Он также выполняет больше работы, чем необходимо, и препятствует посмертной отладке. Чем меньше вы трогаете память после того, как она вам не нужна, тем легче понять, почему что-то сломалось.
Установка указателей на NULL после того, как вы удалили то, на что они указывали, конечно же, не повредит, но часто это своего рода повод для решения более фундаментальной проблемы: почему вы используете указатель в первую очередь? Я вижу две типичные причины:
std :: vector
, и это решает проблему случайного оставления указателей на освобожденную память. Здесь нет указателей. new
, может быть не таким, как тот, который вызывается delete
. Между тем, несколько объектов могли использовать объект одновременно. В этом случае предпочтительнее было бы использовать общий указатель или что-то подобное. Мое практическое правило состоит в том, что если вы оставляете указатели в пользовательском коде, вы делаете это неправильно. Указатель не должен указывать на мусор. Почему нет объекта, который берет на себя ответственность за обеспечение его действительности? Почему его область действия не заканчивается, когда заканчивается указанный объект?
Есть ли объект, отвечающий за обеспечение его действительности? Почему его область действия не заканчивается, когда заканчивается указанный объект? Есть ли объект, отвечающий за обеспечение его действительности? Почему его область действия не заканчивается, когда заканчивается указанный объект?Явное обнуление после удаления настоятельно предлагает читателю, что указатель представляет что-то, что концептуально необязательно . Если бы я увидел, что это делается, я бы начал беспокоиться, что везде в исходном тексте используется указатель, и его сначала нужно протестировать на NULL.
Если это то, что вы на самом деле имеете в виду, лучше сделать это явным в источнике, используя что-то вроде boost :: optional
optional<Foo*> p (new Foo);
// (use p.get(), but must test p for truth first!...)
delete p.get();
p = optional<Foo*>();
Но если вы действительно хотите, чтобы люди знали, что указатель «испортился», я буду на 100% согласен с теми, кто говорит, что лучше всего его погаснуть объема. Затем вы используете компилятор, чтобы предотвратить возможность неправильного разыменования во время выполнения.
Это дитя во всей ванне С ++, не стоит его выбрасывать. :)
Я всегда устанавливаю указатель на NULL
(теперь nullptr
) после удаления объекта (ов), на который он указывает.
Это может помочь поймать много ссылок на освобожденную память (при условии, что ваша платформа дает сбой при deref нулевого указателя).
Он не будет улавливать все ссылки на освобожденную память, если, например, у вас есть копии указателя, лежащие вокруг. Но некоторые лучше, чем ничего.
Это маскирует двойное удаление, но я считаю, что это гораздо менее распространено, чем доступ к уже освобожденной памяти.
Во многих случаях компилятор собирается оптимизировать его. Так что аргумент о том, что в этом нет необходимости, меня не убеждает.
Если вы уже используете RAII, то в вашем коде не так много delete
для начала, поэтому аргумент, что лишние назначение вызывает беспорядок, меня не убеждает.
Часто при отладке удобно видеть нулевое значение, а не устаревший указатель.
Если это все еще вас беспокоит, используйте вместо него умный указатель или ссылку.
Я также установил другие типы дескрипторов ресурсов к значению отсутствия ресурсов, когда ресурс свободен (что обычно есть только в деструкторе оболочки RAII, написанной для инкапсуляции ресурса).
Я работал над большим (9 миллионов операторов) коммерческим продуктом (в основном в C). В какой-то момент мы использовали макромагию, чтобы обнулить указатель при освобождении памяти. Это сразу выявило множество скрытых ошибок, которые были оперативно исправлены. Насколько я помню, у нас никогда не было ошибки с двойным освобождением.
Обновление: Microsoft считает, что это хорошая практика для обеспечения безопасности, и рекомендует эту практику в своих политиках SDL.