Стоит ли указывать указатели на NULL в деструкторе?
Представьте, что у меня есть класс, который выделяет память (забудьте об умных указателях):
class Foo
{
public:
Foo() : bar(new Bar)
{
}
~Foo()
{
delete bar;
}
void doSomething()
{
bar->doSomething();
}
private:
Bar* bar;
};
Как и удаление объектов в деструкторе, также стоит установить их в NULL?
Я предполагаю, что установка указателя на NULL в деструкторе приведенного выше примера является пустой тратой времени.
Ответы
Ответ 1
Поскольку деструктор - это последняя вещь, вызываемая объектом перед тем, как она "умрет", я бы сказал, что нет необходимости устанавливать ее в NULL
после этого.
В любом другом случае я всегда устанавливаю указатель на NULL
после вызова delete
на нем.
Ответ 2
Несколько ответов упоминают, что было бы целесообразно сделать это в сборке DEBUG, чтобы помочь в отладке.
Не делайте этого.
Вы просто потенциально можете скрыть проблему в сборке отладки, которая не скрыта в ваших выпусках, которые вы фактически даете клиентам (что является противоположным эффектом, который должны иметь ваши сборки отладки).
Если вы собираетесь "очистить" указатель в dtor, лучше будет иная идиома - установите указатель на известное значение плохого указателя. Таким образом, если есть какая-то болтливая ссылка на объект где-то, что в конечном итоге пытается использовать указатель, вы получите диагностический сбой вместо кода с ошибкой, избегая использования указателя, потому что он замечает, что он NULL.
Скажем, что doSomething()
выглядел так:
void doSomething()
{
if (bar) bar->doSomething();
}
Затем установка bar
в NULL только помогла скрыть ошибку, если была ссылка на удаленный объект Foo
, который вызывал Foo::doSomething()
.
Если очистка указателя выглядела так:
~Foo()
{
delete bar;
if (DEBUG) bar = (bar_type*)(long_ptr)(0xDEADBEEF);
}
У вас может быть больше шансов поймать ошибку (хотя просто оставить только bar
, вероятно, будет иметь аналогичный эффект).
Теперь, если что-либо имеет обвисшую ссылку на объект Foo
, который был удален, любое использование bar
не позволит ссылаться на него из-за проверки NULL - он с удовольствием попытается использовать указатель, и вы будете получите ошибку, которую вы можете исправить, вместо того, чтобы ничего плохого происходило в отладочных сборках, но все еще используемая (в противном случае) болтливая ссылка в ваших версиях выпуска клиента.
Когда вы компилируете в режиме отладки, вероятность того, что менеджер кучи отладки уже будет делать это для вас в любом случае (по крайней мере, диспетчер кучи памяти отладки MSVC перезапишет освобожденную память с помощью 0xDD, чтобы указать, что память мертва/освобождены).
Главное, что если вы используете исходные указатели как члены класса, не устанавливайте указатели на NULL, когда запускается dtor.
Это правило может также применяться к другим исходным указателям, но это зависит от того, как именно используется указатель.
Ответ 3
Да, это пустая трата времени.
Ответ 4
Вы не должны по двум причинам:
-
это помогает отлаживать, но в современных средах удаленные объекты обычно перезаписываются уже с распознаваемым битовым шаблоном в сборках отладки.
-
В большом приложении это может значительно снизить производительность выключения.
В худшем случае закрытие приложения означает вызов десятков различных деструкторов и запись на сотни страниц кучи, которые в настоящее время заменяются на диск.
Ответ 5
Прежде всего, это практика С и противоречивая. Некоторые утверждают (для C), что он скрывает ошибки, которые раньше появлялись бы, если бы они не использовались, и невозможно отличить использование освобожденной части памяти от использования той, которая никогда не выделялась...
Теперь в С++? Это бесполезно, но не по той же причине, что и в C.
В С++ это ошибка использования delete
. Если бы вы использовали интеллектуальные указатели, вы бы не беспокоились об этом, и вы не рискнули бы протекать (т.е. Вы уверены, что ваш конструктор копирования и оператор присваивания являются безопасными для исключения? Thread safe?)
Наконец, в деструкторе это действительно бесполезно... доступ к любому полю объекта после запуска деструктора - это поведение undefined. Вы освободили память, так что там может быть что-то еще написанное, перезаписав ваш аккуратный NULL. И на самом деле, как работают распределители памяти, часто приходится перераспределять сначала недавно освобожденные зоны: это повышает производительность кеширования... и это даже более верно (ха-ха!), Если мы говорим о стеке, конечно.
Мое мнение: SAFE_DELETE
является признаком надвигающейся гибели.
Ответ 6
Возможно, это стоит того, чтобы отлаживать причины.
Ответ 7
Как правило, нет, нет необходимости указывать указатели на NULL
явно после удаления их в деструкторе, хотя это может быть полезным при отладке при проверке класса, чтобы указать, был ли ресурс правильно освобожден или нет.
Общей идиомой является объявление макроса SAFE_DELETE
, который удаляет указатель и устанавливает для него NULL
:
#define SAFE_DELETE(x) delete (x); x = NULL
SAFE_DELETE(bar)
Это особенно полезно в случаях, когда указатель может впоследствии использоваться повторно.
Ответ 8
IMO его стоит в режиме DEBUG. Я часто нахожу это полезным. В режиме RELEASE он обычно пропускается компилятором из-за оптимизации кода, поэтому вы не должны полагаться на это в своем производственном коде.
Ответ 9
Я бы избегал необработанных указателей любой ценой, например, с умной точкой и без нее, где явно задано значение NULL:
С
class foo
{
public:
foo() : m_something( new int ) { }
void doStuff()
{
// delete + new again - for whatever reason this might need doing
m_something.reset( new int );
}
private:
std::unique_ptr<int> m_something; // int as an example, no need for it to be on the heap in "real" code
}
Без:
class foo
{
public:
foo() : m_something( new int ) { }
~foo()
{
delete m_something;
}
void doStuff()
{
delete m_something;
// Without this, if the next line throws then the dtor will do a double delete
m_something = nullptr;
m_something = new int;
}
private:
int* m_something
}
Ответ 10
Нет, это не стоит.
Но если вы хотите быть последовательным, вы должны, вероятно, сделать это.
Что бы я обычно делал, так это создать функцию Free, которую я могу использовать каждый раз, когда мне нужно освобождать выделенные данные (при необходимости, больше бесплатных функций). В этих функциях рекомендуется указывать указатели на NULL (или идентификатор на недопустимое значение).
Ответ 11
Я думаю, что всегда стоит делать это (даже если вам это не требуется). Я установил указатель на NULL, чтобы указать, что память, на которую указывает указывает, не требуется освобождение.
Также, если это полезно для проверки, указатель действителен перед его использованием.
if (NULL == pSomething)
{
// Safe to operate on pSomething
}
else
{
// Not safe to operate on pSomething
}
Поместите NULL сначала в условие if, чтобы предотвратить непреднамеренно установку pSomething в NULL, когда вы проскальзываете и пропускаете второй '='. Вы получаете ошибку компиляции, а не ошибку, требующую времени для отслеживания.
Ответ 12
Хорошая практика всегда устанавливать указатели на NULL после их удаления. Кроме того, некоторые инструменты проверки кода обеспечивают это.