Это - хорошая практика для ОБНУЛЕНИЯ указателя после удаления его?

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

Каковы проблемы со следующим кодом?

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

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

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

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

139
задан Community 23 May 2017 в 12:10
поделиться

16 ответов

Установка указателя на 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: :

80
ответ дан 23 November 2019 в 23:21
поделиться

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

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

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

0
ответ дан 23 November 2019 в 23:21
поделиться

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

0
ответ дан 23 November 2019 в 23:21
поделиться

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

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

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

0
ответ дан 23 November 2019 в 23:21
поделиться

Да.

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

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

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

#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 указатель звонящего.

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

1
ответ дан 23 November 2019 в 23:21
поделиться

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

0
ответ дан 23 November 2019 в 23:21
поделиться

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

Вот что вы добавили в свой вопрос в виде маркированного списка:


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

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

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

Итак, если если есть сомнения, просто обнуляйте его.

Сказав это, если вы чувствуете, что вам нужно явно обнулить некоторый указатель, то для меня это звучит так, как будто вы недостаточно разделили метод,

1
ответ дан 23 November 2019 в 23:21
поделиться

Я немного изменю ваш вопрос:

Вы бы использовали неинициализированный указатель? Вы знаете, что вы не сделали установите значение NULL или выделите ему память указывает на?

Существует два сценария, в которых установка указателя на NULL может быть пропущена:

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

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

2
ответ дан 23 November 2019 в 23:21
поделиться

Как говорили другие, удалить ptr; ptr = 0; не заставит демонов вылететь из вашего носа. Тем не менее, он поощряет использование ptr как своего рода флага. Код засоряется delete и установкой указателя на NULL . Следующий шаг - разбросать if (arg == NULL) return; по вашему коду, чтобы защитить от случайного использования указателя NULL . Проблема возникает, когда проверки на NULL становятся вашим основным средством проверки состояния объекта или программы.

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

3
ответ дан 23 November 2019 в 23:21
поделиться

Если после удалить есть еще код, то да. Когда указатель удаляется в конструкторе или в конце метода или функции, нет.

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

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

7
ответ дан 23 November 2019 в 23:21
поделиться

Во-первых, есть много существующих вопросов по этой и тесно связанным темам, например Почему не удалить, установить указатель на NULL? .

В ваш код, проблема в том, что происходит (используйте p). Например, если где-то у вас есть такой код:

Foo * p2 = p;

, то установка p на NULL очень мало дает, так как у вас все еще есть указатель p2, о котором нужно беспокоиться.

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

12
ответ дан 23 November 2019 в 23:21
поделиться

У меня есть еще лучшая передовая практика: по возможности закройте область видимости переменной!

{
    Foo* pFoo = new Foo;
    // use pFoo
    delete pFoo;
}
42
ответ дан 23 November 2019 в 23:21
поделиться

Если у вас нет другого ограничения, которое заставляет вас либо устанавливать, либо не устанавливать указатель в NULL после его удаления (одно такое ограничение было упомянуто Нилом Баттервортом ), тогда я лично предпочитаю оставить все как есть.

Для меня вопрос не в том, "хорошая ли это идея?" но «какое поведение я бы предотвратил или позволил бы добиться успеха, сделав это?» Например, если это позволяет другому коду увидеть, что указатель больше не доступен, почему другой код даже пытается просмотреть освобожденные указатели после того, как они освобождены? Обычно это ошибка.

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

2
ответ дан 23 November 2019 в 23:21
поделиться

Установка указателей на NULL после того, как вы удалили то, на что они указывали, конечно же, не повредит, но часто это своего рода повод для решения более фундаментальной проблемы: почему вы используете указатель в первую очередь? Я вижу две типичные причины:

  • Вы просто хотели, чтобы что-то было размещено в куче. В этом случае упаковка его в объект RAII была бы намного безопаснее и чище. Закройте область действия объекта RAII, когда объект больше не нужен. Так работает std :: vector , и это решает проблему случайного оставления указателей на освобожденную память. Здесь нет указателей.
  • Или, возможно, вам нужна сложная семантика совместного владения. Указатель, возвращаемый из new , может быть не таким, как тот, который вызывается delete . Между тем, несколько объектов могли использовать объект одновременно. В этом случае предпочтительнее было бы использовать общий указатель или что-то подобное.

Мое практическое правило состоит в том, что если вы оставляете указатели в пользовательском коде, вы делаете это неправильно. Указатель не должен указывать на мусор. Почему нет объекта, который берет на себя ответственность за обеспечение его действительности? Почему его область действия не заканчивается, когда заканчивается указанный объект?

Есть ли объект, отвечающий за обеспечение его действительности? Почему его область действия не заканчивается, когда заканчивается указанный объект?

Есть ли объект, отвечающий за обеспечение его действительности? Почему его область действия не заканчивается, когда заканчивается указанный объект?

54
ответ дан 23 November 2019 в 23:21
поделиться

Явное обнуление после удаления настоятельно предлагает читателю, что указатель представляет что-то, что концептуально необязательно . Если бы я увидел, что это делается, я бы начал беспокоиться, что везде в исходном тексте используется указатель, и его сначала нужно протестировать на 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% согласен с теми, кто говорит, что лучше всего его погаснуть объема. Затем вы используете компилятор, чтобы предотвратить возможность неправильного разыменования во время выполнения.

Это дитя во всей ванне С ++, не стоит его выбрасывать. :)

2
ответ дан 23 November 2019 в 23:21
поделиться

Я всегда устанавливаю указатель на NULL (теперь nullptr ) после удаления объекта (ов), на который он указывает.

  1. Это может помочь поймать много ссылок на освобожденную память (при условии, что ваша платформа дает сбой при deref нулевого указателя).

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

  3. Это маскирует двойное удаление, но я считаю, что это гораздо менее распространено, чем доступ к уже освобожденной памяти.

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

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

  6. Часто при отладке удобно видеть нулевое значение, а не устаревший указатель.

  7. Если это все еще вас беспокоит, используйте вместо него умный указатель или ссылку.

Я также установил другие типы дескрипторов ресурсов к значению отсутствия ресурсов, когда ресурс свободен (что обычно есть только в деструкторе оболочки RAII, написанной для инкапсуляции ресурса).

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

Обновление: Microsoft считает, что это хорошая практика для обеспечения безопасности, и рекомендует эту практику в своих политиках SDL.

30
ответ дан 23 November 2019 в 23:21
поделиться
Другие вопросы по тегам:

Похожие вопросы: