Подтвердить что ты не робот

Стоит ли указывать указатели на NULL в деструкторе?

Представьте, что у меня есть класс, который выделяет память (забудьте об умных указателях):

class Foo
{
public:
  Foo() : bar(new Bar)
  {
  }

  ~Foo()
  {
    delete bar;
  }

  void doSomething()
  {
    bar->doSomething();
  }

private:
  Bar* bar;
};

Как и удаление объектов в деструкторе, также стоит установить их в NULL?

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

4b9b3361

Ответ 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 после их удаления. Кроме того, некоторые инструменты проверки кода обеспечивают это.