Является ли хорошей практикой обнулять указатель после его удаления? - PullRequest
134 голосов
/ 19 декабря 2009

Я начну с того, что используйте умные указатели, и вам никогда не придется об этом беспокоиться.

В чем проблемы со следующим кодом?

Foo * p = new Foo;
// (use p)
delete p;
p = NULL;

Это было вызвано ответом и комментариями к другому вопросу. Один комментарий от Нейла Баттерворта вызвал несколько голосов:

Установка указателей на NULL после удаления не является универсальной хорошей практикой в ​​C ++. Бывают моменты, когда это полезно, и времена, когда это бессмысленно и может скрывать ошибки.

Есть много обстоятельств, когда это не помогло бы. Но по моему опыту, это не повредит. Кто-нибудь просветите меня.

Ответы [ 18 ]

2 голосов
/ 28 февраля 2012

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

Многие аргументы против присвоения 0 предполагают, что может скрыть ошибку или усложнить поток управления. По сути, это либо ошибка восходящего потока (не ваша ошибка (извините за плохой каламбур)), либо другая ошибка от имени программиста - возможно, даже указание на то, что выполнение программы стало слишком сложным.

Если программист хочет ввести использование указателя, который может быть нулевым, в качестве специального значения и записать все необходимые уклонения от этого, это усложнение, которое они сознательно ввели. Чем лучше карантин, тем быстрее вы обнаружите случаи злоупотреблений и тем меньше они смогут распространяться в других программах.

Хорошо структурированные программы могут быть разработаны с использованием функций C ++, чтобы избежать этих случаев. Вы можете использовать ссылки, или вы можете просто сказать «передача / использование пустых или недействительных аргументов - это ошибка» - подход, который в равной степени применим к контейнерам, таким как интеллектуальные указатели. Повышение согласованности и правильности поведения не позволяет этим ошибкам уйти далеко.

Оттуда у вас есть только очень ограниченная область действия и контекст, где может существовать нулевой указатель (или это разрешено).

То же самое можно применить к указателям, которые не const. Следование значению указателя тривиально, поскольку его область действия настолько мала, а неправильное использование проверяется и хорошо определяется. Если ваш инструментарий и инженеры не могут следовать программе после быстрого чтения, или если выполняется неправильная проверка ошибок или противоречивый / мягкий процесс выполнения программы, у вас есть другие, более серьезные проблемы.

Наконец, ваш компилятор и окружение, вероятно, имеют некоторые средства защиты на время, когда вы хотели бы вводить ошибки (писанины), обнаруживать доступ к освобожденной памяти и перехватывать другие связанные UB. Вы также можете внедрить аналогичную диагностику в свои программы, часто не затрагивая существующие программы.

1 голос
/ 19 декабря 2009

Позвольте мне расширить то, что вы уже вложили в свой вопрос.

Вот что вы задали в своем вопросе в форме маркера:


Установка указателей на NULL после удаления не является универсальной хорошей практикой в ​​C ++. Есть моменты, когда:

  • это хорошая вещь
  • и времена, когда это бессмысленно и может скрывать ошибки.

Тем не менее, нет раз , когда это плохо ! Вы будете не вводить больше ошибок, явно обнуляя их, вы не будете пропускать память, вы не будете вызывать неопределенное поведение .

Так что, если сомневаетесь, просто обнулите его.

Сказав это, если вы чувствуете, что должны явно обнулить какой-то указатель, то для меня это звучит так, как будто вы недостаточно разбили метод и должны рассмотреть метод рефакторинга, называемый "Извлечь метод", чтобы разделить Метод на отдельные части.

1 голос
/ 27 января 2014

«Бывают моменты, когда это полезно, а иногда бессмысленно и может скрывать ошибки»

Я вижу две проблемы: Этот простой код:

delete myObj;
myobj = 0

становится для лайнера в многопоточной среде:

lock(myObjMutex); 
delete myObj;
myobj = 0
unlock(myObjMutex);

«Лучшая практика» Дона Нойфельда не всегда применима. Например. в одном автомобильном проекте мы должны были установить указатели на 0 даже в деструкторах. Я могу представить, что в программном обеспечении, критичном для безопасности, такие правила не редкость. Проще (и мудрее) следовать им, чем пытаться убедить команда / проверка кода для каждого использования указателя в коде, что строка, обнуляющая этот указатель, является избыточной.

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

try{  
   delete myObj; //exception in destructor
   myObj=0
}
catch
{
   //myObj=0; <- possibly resource-leak
}

if (myObj)
  // use myObj <--undefined behaviour

В таком коде вы либо создаете утечку ресурсов и откладываете проблему, либо происходит сбой процесса.

Итак, эти две проблемы, спонтанно возникающие в моей голове (Херб Саттер наверняка расскажет больше), делают для меня все вопросы типа «Как избежать использования умных указателей и безопасно выполнять работу с обычными указателями» как устаревшие .

1 голос
/ 19 декабря 2009

Да.

Единственный «вред», который он может сделать, - это ввести в вашу программу неэффективность (ненужную операцию хранения), но эти накладные расходы будут незначительными по сравнению со стоимостью выделения и освобождения блока памяти в большинстве случаев.

Если вы этого не сделаете, у вас будет однажды появятся некоторые неприятные ошибки разыменования указателя.

Я всегда использую макрос для удаления:

#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;
}

редактировать

Да, эти методы нарушают некоторые правила использования макросов (и да, в наши дни вы, вероятно, могли бы достичь того же результата с помощью шаблонов), но, используя многие годы, я никогда не получал доступ к мертвой памяти - одна из самых отвратительных и трудных и наиболее трудоемких для устранения проблем, с которыми вы можете столкнуться. На практике в течение многих лет они эффективно устраняли класс ошибок из каждой команды, в которой я их представил.

Есть также много способов, как вы могли бы реализовать вышесказанное - я просто пытаюсь проиллюстрировать идею принуждения людей к NULL-указателю, если они удаляют объект, вместо предоставления средств для освобождения памяти, которая не равна NULL. указатель звонящего.

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

0 голосов
/ 19 декабря 2009

Всегда есть висячие указатели , о которых стоит беспокоиться.

0 голосов
/ 19 декабря 2009

Я могу себе представить, что указатель на NULL после удаления может быть полезен в редких случаях, когда существует законный сценарий повторного использования его в одной функции (или объекте). В противном случае это не имеет смысла - указатель должен указывать на что-то значимое, пока оно существует - точка.

0 голосов
/ 19 декабря 2009

Если код не относится к наиболее критичной для вашего приложения части, сохраняйте его простым и используйте shared_ptr:

shared_ptr<Foo> p(new Foo);
//No more need to call delete

Он выполняет подсчет ссылок и является потокобезопасным. Вы можете найти его в tr1 (пространство имен std :: tr1, #include ) или, если ваш компилятор не предоставляет его, получить его из boost.

0 голосов
/ 19 декабря 2009

Если вы собираетесь перераспределить указатель перед тем, как снова его использовать (разыменовать его, передать в функцию и т. Д.), Сделать указатель NULL просто дополнительной операцией. Однако, если вы не уверены, будет ли он перераспределен или нет до его повторного использования, установка NULL - хорошая идея.

Как уже говорили многие, гораздо проще использовать умные указатели.

Редактировать: Как сказал Томас Мэтьюз в этом предыдущем ответе , если указатель удален в деструкторе, нет необходимости назначать ему NULL, поскольку он не будет использоваться снова, объект уже разрушен.

...