Я начну с того, что использую умные указатели, и вам никогда не придется об этом беспокоиться.
Какие проблемы со следующим кодом?
Foo * p = new Foo;
// (use p)
delete p;
p = NULL;
Это было вызвано ответом и комментариями к другому вопросу. Один комментарий от Нила Баттерворта вызвал несколько голосов:
Установка указателей на NULL после удаления не является универсальной хорошей практикой в C ++. Есть моменты, когда это полезно, и времена, когда это бессмысленно и может скрывать ошибки.
Есть много обстоятельств, когда это не поможет. Но по моему опыту, это не повредит. Кто-нибудь просветите меня.
c++
pointers
null
dynamic-allocation
Марк Рэнсом
источник
источник
delete
Тем не менее, это безопасно для нулевого указателя, что является одной из причин, по которой обнуление указателя может быть хорошим.Ответы:
Установка указателя на 0 (который является «нулевым» в стандартном C ++, определение NULL из C несколько отличается) позволяет избежать сбоев при двойном удалении.
Учтите следующее:
В то время как:
Другими словами, если вы не установите удаленные указатели на 0, у вас возникнут проблемы, если вы делаете двойное удаление. Аргумент против установки указателей на 0 после удаления может заключаться в том, что это просто маскирует двойные ошибки удаления и оставляет их необработанными.
Очевидно, что лучше не иметь ошибок двойного удаления, но в зависимости от семантики владения и жизненных циклов объектов на практике это может быть трудно достичь. Я предпочитаю замаскированную ошибку двойного удаления над UB.
Наконец, в качестве руководства по распределению объектов, я предлагаю вам взглянуть на
std::unique_ptr
строгое / единичное владение,std::shared_ptr
на общее владение или на другую реализацию интеллектуального указателя, в зависимости от ваших потребностей.источник
NULL
может маскировать ошибку двойного удаления. (Некоторые могут посчитать, что эта маска на самом деле является решением - да, но не очень хорошим, так как она не доходит до корня проблемы.) Но не установив ее в NULL, маски (FAR!) Больше общие проблемы доступа к данным после того, как они были удалены.unique_ptr
, что делает то, чтоauto_ptr
пытался сделать, семантикой перемещения.Установка указателей на NULL после того, как вы удалили то, на что оно указывало, определенно не может повредить, но это часто немного помогает при более серьезной проблеме: почему вы вообще используете указатель? Я вижу две типичные причины:
std::vector
работает, и это решает проблему случайного оставления указателей для освобождения памяти вокруг. Там нет указателей.new
может отличаться от того, которыйdelete
вызывается. Несколько объектов, возможно, использовали объект одновременно. В этом случае общий указатель или что-то подобное было бы предпочтительным.Мое эмпирическое правило заключается в том, что если вы оставляете указатели в коде пользователя, вы делаете это неправильно. Указатель не должен быть там, чтобы указывать на мусор в первую очередь. Почему нет объекта, несущего ответственность за обеспечение его действительности? Почему его область действия не заканчивается, когда указатель указывает на объект?
источник
new
.У меня есть еще лучшая практика: по возможности заканчивайте область видимости переменной!
источник
Я всегда устанавливаю указатель на
NULL
(сейчасnullptr
) после удаления объекта (ов), на который он указывает.Это может помочь отловить множество ссылок на освобожденную память (если предположить, что ваша платформа неисправна по нулевому указателю).
Он не будет перехватывать все ссылки на свободную память, если, например, у вас есть копии указателя. Но некоторые лучше, чем ничего.
Это будет маскировать двойное удаление, но я считаю, что они гораздо реже, чем доступ к уже освобожденной памяти.
Во многих случаях компилятор собирается оптимизировать его. Таким образом, аргумент, что это ненужно, не убеждает меня.
Если вы уже используете RAII, то не так много
delete
в вашем коде s, поэтому аргумент о том, что дополнительное назначение вызывает беспорядок, не убеждает меня.При отладке часто бывает удобно видеть нулевое значение, а не устаревший указатель.
Если это все еще вас беспокоит, используйте вместо этого умный указатель или ссылку.
Я также установил для других типов дескрипторов ресурсов значение no-resource, когда ресурс free'd (который обычно находится только в деструкторе оболочки RAII, написанной для инкапсуляции ресурса).
Я работал над большим (9 миллионов заявлений) коммерческим продуктом (в основном на С). В какой-то момент мы использовали макро-магию для обнуления указателя при освобождении памяти. Это сразу выявило множество скрывающихся ошибок, которые были быстро исправлены. Насколько я помню, у нас никогда не было двойной ошибки.
Обновление: Microsoft считает, что это хорошая практика для обеспечения безопасности, и рекомендует эту практику в своих политиках SDL. Очевидно, что MSVC ++ 11 автоматически вытеснит удаленный указатель (во многих случаях), если вы скомпилируете с параметром / SDL.
источник
Во-первых, существует множество существующих вопросов по этой и тесно связанным темам, например, Почему не удаляется установка указателя на NULL? ,
В вашем коде проблема, что происходит (используйте p). Например, если где-то у вас есть такой код:
затем установка p в NULL завершается очень мало, так как у вас все еще есть указатель p2, о котором нужно беспокоиться.
Нельзя сказать, что установка указателя на NULL всегда бессмысленна. Например, если p была переменной-членом, указывающей на ресурс, время жизни которого не было точно таким же, как у класса, содержащего p, то установка p в NULL могла бы быть полезным способом указания наличия или отсутствия ресурса.
источник
Если после кода больше
delete
, да. Когда указатель удаляется в конструкторе или в конце метода или функции, нет.Смысл этой притчи - напомнить программисту во время выполнения, что объект уже удален.
Еще лучшая практика - использовать интеллектуальные указатели (совместно используемые или с областью видимости), которые автоматически удаляют целевые объекты.
источник
Как уже говорили другие,
delete ptr; ptr = 0;
это не заставит демонов вылететь из вашего носа. Тем не менее, это поощряет использование вptr
качестве своего рода флага. Код засоряетсяdelete
и устанавливает указатель наNULL
. Следующий шаг - разбросатьif (arg == NULL) return;
ваш код, чтобы защитить его от случайного использованияNULL
указателя. Проблема возникает после проверкиNULL
становятся вашими основными средствами проверки состояния объекта или программы.Я уверен, что в коде есть какой-то запах использования указателя в качестве флага, но я его не нашел.
источник
NULL
не является допустимым значением, тогда вам, вероятно, следует использовать ссылку.Я немного изменю ваш вопрос:
Существует два сценария, в которых установка указателя на NULL может быть пропущена:
Между тем, утверждение о том, что установка указателя на NULL может скрывать ошибки для меня, звучит как утверждение, что вы не должны исправлять ошибку, потому что исправление может скрывать другую ошибку. Единственные ошибки, которые могут отображаться, если указатель не установлен в NULL, будут те, которые пытаются использовать указатель. Но установка его в NULL фактически вызовет точно такую же ошибку, как если бы вы использовали его с освобожденной памятью, не так ли?
источник
Если у вас нет другого ограничения, которое заставляет вас устанавливать или не устанавливать указатель в NULL после удаления (одно из таких ограничений было упомянуто Нейлом Баттервортом ), тогда я лично предпочитаю оставить это так.
Для меня вопрос не "это хорошая идея?" но "какое поведение я бы помешал или позволил добиться успеха, делая это?" Например, если это позволяет другому коду увидеть, что указатель больше не доступен, почему другой код даже пытается просмотреть освобожденные указатели после их освобождения? Обычно это ошибка.
Это также делает больше работы, чем необходимо, а также препятствует посмертной отладке. Чем меньше вы касаетесь памяти после того, как она вам не нужна, тем легче понять, почему что-то сломалось. Много раз я полагался на то, что память находится в состоянии, аналогичном тому, когда возникала конкретная ошибка, для диагностики и исправления указанной ошибки.
источник
Явный обнуление после удаления настоятельно рекомендует читателю, что указатель представляет собой что-то, что является концептуально необязательным . Если бы я увидел, что это сделано, я бы начал беспокоиться о том, что везде в источнике используется указатель, что он должен быть сначала проверен на NULL.
Если это то, что вы на самом деле имеете в виду, лучше сделать это явно в источнике, используя что-то вроде boost :: option
Но если вы действительно хотите, чтобы люди знали, что указатель «испортился», я согласен на 100% согласиться с теми, кто говорит, что лучше всего сделать так, чтобы он вышел из области видимости. Затем вы используете компилятор, чтобы предотвратить возможность неправильных разыменований во время выполнения.
Это ребенок во всей воде С ++, не должен выбрасывать его. :)
источник
В хорошо структурированной программе с соответствующей проверкой ошибок нет причин не присваивать ей значение null.
0
в этом контексте выделяется как общепризнанное недействительное значение. Терпеть неудачу и терпеть неудачу скоро.Многие аргументы против присвоения
0
предполагают, что это может скрыть ошибку или усложнить поток управления. По сути, это либо ошибка восходящего потока (не ваша ошибка (извините за плохой каламбур)), либо другая ошибка от имени программиста - возможно, даже указание на то, что выполнение программы стало слишком сложным.Если программист хочет ввести использование указателя, который может быть нулевым, в качестве специального значения и записать все необходимые уклонения от этого, это усложнение, которое они сознательно ввели. Чем лучше карантин, тем быстрее вы обнаружите случаи злоупотреблений и тем меньше они смогут распространяться в других программах.
Хорошо структурированные программы могут быть разработаны с использованием функций C ++, чтобы избежать этих случаев. Вы можете использовать ссылки или просто сказать «передача / использование пустых или недействительных аргументов - это ошибка» - подход, который в равной степени применим к контейнерам, таким как интеллектуальные указатели. Увеличение последовательного и правильного поведения не позволяет этим ошибкам далеко уйти.
Оттуда у вас есть только очень ограниченная область действия и контекст, где может существовать нулевой указатель (или это разрешено).
То же самое может быть применено к указателям, которые не являются
const
. Отслеживание значения указателя является тривиальным, поскольку его область действия настолько мала, а неправильное использование проверяется и хорошо определяется. Если ваш инструментарий и инженеры не могут следовать программе после быстрого чтения, или если выполняется неправильная проверка ошибок или непоследовательный / мягкий ход программы, у вас есть другие, более серьезные проблемы.Наконец, ваш компилятор и окружение, вероятно, имеют некоторые средства защиты для случаев, когда вы хотели бы вводить ошибки (писанины), обнаруживать доступ к освобожденной памяти и перехватывать другие связанные UB. Вы также можете внедрить аналогичную диагностику в свои программы, часто не затрагивая существующие программы.
источник
Позвольте мне расширить то, что вы уже положили в свой вопрос.
Вот что вы задали в своем вопросе в форме маркера:
Установка указателей на NULL после удаления не является универсальной хорошей практикой в C ++. Есть моменты, когда:
Однако не бывает случаев, когда это плохо ! Вы не будете вносить больше ошибок, явно обнуляя их, вы не потеряете память, вы не вызовете неопределенного поведения .
Так что, если сомневаетесь, просто обнулите его.
Сказав это, если вы чувствуете, что должны явно обнулить некоторый указатель, то для меня это звучит так, как будто вы недостаточно разбили метод и должны рассмотреть подход рефакторинга под названием «Извлечь метод», чтобы разделить метод на отдельные части.
источник
Да.
Единственный «вред», который он может сделать, - это ввести неэффективность (ненужную операцию хранилища) в вашу программу, но эти накладные расходы будут незначительными по сравнению со стоимостью выделения и освобождения блока памяти в большинстве случаев.
Если вы этого не сделаете, однажды у вас будут неприятные ошибки разыменования указателя.
Я всегда использую макрос для удаления:
(и аналогично для массива free (), освобождая дескрипторы)
Вы также можете написать методы «самостоятельного удаления», которые принимают ссылку на указатель вызывающего кода, поэтому они принудительно указывают указатель вызывающего кода на NULL. Например, чтобы удалить поддерево многих объектов:
редактировать
Да, эти методы нарушают некоторые правила использования макросов (и да, в наши дни вы, вероятно, могли бы достичь того же результата с помощью шаблонов), но, используя многие годы, я никогда не обращался к мертвой памяти - одной из самых отвратительных и трудных и наиболее трудоемкий для устранения проблем, с которыми вы можете столкнуться. На практике в течение многих лет они эффективно устраняли класс ошибок в каждой команде, в которой я их представил.
Есть также много способов, как вы могли бы реализовать вышесказанное - я просто пытаюсь проиллюстрировать идею принуждения людей к NULL-указателю, если они удаляют объект, вместо того, чтобы предоставить им средство для освобождения памяти, которая не NULL, указатель вызывающей стороны ,
Конечно, приведенный выше пример является лишь шагом к автоматическому указателю. Что я и не предлагал, потому что ОП специально спрашивал о том, чтобы не использовать автоматический указатель.
источник
anObject->Delete(anObject)
лишить законной силыanObject
указатель. Это просто страшно. Вы должны создать статический метод для этого, чтобы вы были вынуждены делать этоTreeItem::Delete(anObject)
как минимум.«Бывают моменты, когда это полезно, и времена, когда это бессмысленно и может скрывать ошибки»
Я вижу две проблемы: этот простой код:
делается для лайнера в многопоточной среде:
«Лучшая практика» Дона Нейфельда не всегда применима. Например, в одном автомобильном проекте мы должны были установить указатели на 0 даже в деструкторах. Я могу представить, что в программном обеспечении, критичном для безопасности, такие правила не редкость. Проще (и разумнее) следовать им, чем пытаться убедить команду / средство проверки кода для каждого использования указателя в коде, что строка, обнуляющая этот указатель, является избыточной.
Другая опасность - полагаться на эту технику в коде, использующем исключения:
В таком коде вы либо создаете утечку ресурсов и откладываете проблему, либо происходит сбой процесса.
Итак, эти две проблемы, спонтанно возникающие в моей голове (Херб Саттер наверняка расскажет больше), делают для меня все вопросы типа «Как избежать использования умных указателей и безопасного выполнения работы с обычными указателями» как устаревшие.
источник
Всегда есть о чем беспокоиться.
источник
Если вы собираетесь перераспределить указатель перед тем, как снова его использовать (разыменовать его, передать в функцию и т. Д.), Сделать указатель NULL просто дополнительной операцией. Однако, если вы не уверены, будет ли он перераспределен или нет до его повторного использования, установка NULL - хорошая идея.
Как уже говорили многие, гораздо проще использовать умные указатели.
Редактировать: Как сказал Томас Мэтьюз в этом предыдущем ответе , если указатель удален в деструкторе, нет необходимости назначать ему значение NULL, поскольку он не будет использоваться снова, поскольку объект уже уничтожается.
источник
Я могу представить, что установка указателя на NULL после удаления будет полезна в редких случаях, когда существует законный сценарий повторного использования его в одной функции (или объекте). В противном случае это не имеет смысла - указатель должен указывать на что-то значимое, пока оно существует - точка.
источник
Если код не относится к наиболее критичной к производительности части вашего приложения, сделайте его простым и используйте shared_ptr:
Он выполняет подсчет ссылок и является потокобезопасным. Вы можете найти его в tr1 (пространство имен std :: tr1, #include <memory>) или, если ваш компилятор не предоставляет его, получить его из boost.
источник