Не обращайтесь к объекту за пределами времени его жизни

Доступ к обоъекту за пределами времени его жизни - неопределенное поведение.

Начало времени жизни:

  • Для объектов с тривиальной инициализацией: когда выделена память подходящего размера и выравнивания
  • Для объектов с нетривиальной инициализацией: когда конструктор завершает выполнение

Конец времени жизни:

  • Для объектов с тривиальным деструктором: когда память освобождается или переиспользуется
  • Для объектов с нетривиальным деструктором: когда начинается выполнение деструктора

Категории времени жизни:

  • Автоматическое (automatic storage duration)
void func() {
    int x = 42; // начало: при выполнении объявления
}               // конец: при выходе из области видимости
  • Статическое (static storage duration)
static int global = 0;    // начало: до main()
                          // конец: после main()
  • Динамическое (dynamic storage duration)
int* p = new int(10);     // начало: после new
delete p;                 // конец: после delete
  • Потоковое (thread storage duration)
thread_local int t = 0;   // начало: при старте потока
                          // конец: при завершении потока

Соблюдайте следующие рекомендации:

  1. Никогда не возвращайте ссылки/указатели на локальные объекты

Неправильно:

int& getValue() {
    int local = 42;
    return local;  // висячая ссылка
}

Правильно:

int getValue() {
    int local = 42;
    return local;  // возврат по значению
}
  1. Проверяйте указатели, включая умные, на валидность

Неправильно:

void foo(Widget* widget) {
    widget->show();
}

Правильно:

void foo(Widget* widget) {
    if (widget)
        widget->show();
}
  1. При освобождении сырых указателей, зануляйте их, если указатель может быть повторно использован

Неправильно:

delete widget;
// ...
if (widget)
    widget->show();

Правильно:

delete widget;
widget = nullptr;
// ...
if (widget)
    widget->show();