Допустим, указатель на объект равен nullptr. Можно ли безопасно вызвать через него нестатический метод, если тело метода не обращается к полям объекта?
Нет. Вызов нестатического метода через нулевой указатель имеет неопределённое поведение, даже если тело метода не использует поля объекта. То, что конкретный компилятор иногда успешно выполняет такой вызов, не делает его корректным.
Модель C++ рассматривает нестатический метод как функцию, связанную с объектом через неявный указатель this. Такой метод предназначен для работы с конкретным экземпляром, а nullptr не обозначает существующий объект.
Язык допускает низкоуровневую работу с указателями, но не превращает нулевой указатель в специальный «пустой объект». Поэтому корректность вызова определяется существованием объекта, а не фактическим использованием его полей внутри тела метода.
Разработчик может решить, что метод безопасен, поскольку он возвращает константу или обращается только к статическим данным. Однако при вызове через нулевой указатель уже нарушается требование о корректном объекте вызова.
Для невиртуального метода компилятор иногда генерирует обычный переход на адрес функции, и программа может внешне работать. Виртуальный вызов обычно требует чтения таблицы виртуальных функций из объекта и может завершиться аварийно сразу, но оба варианта не дают гарантированного поведения.
Нестатический метод получает неявный параметр this. Вызов через указатель концептуально требует обратиться к объекту, на который указывает этот указатель; при значении nullptr такого объекта нет. Поэтому отсутствие обращения к полям не устраняет проблему.
Метод status не читает поля, но вызов всё равно некорректен. Оптимизатор вправе исходить из того, что корректный вызов нестатического метода получает действительный объект, поэтому нельзя строить логику на наблюдаемом поведении конкретной сборки.
Если операция не требует объекта, её следует сделать статической функцией, свободной функцией или вызвать через гарантированно существующий объект. Проверка указателя перед вызовом допустима, когда отсутствие объекта является штатным состоянием: например, можно сначала проверить указатель, а затем вызвать метод.
Важно отличать нестатический метод от статического. Статический метод не получает this и не зависит от экземпляра, хотя вызывать его через выражение с указателем — плохая практика: она маскирует отсутствие связи с объектом и ухудшает читаемость.
В обработчике устройств указатель на необязательный объект иногда оставался равным nullptr. Разработчик вызывал через него метод диагностики, который не использовал поля, и тестовая сборка работала, потому что метод был невиртуальным.
Рассматривались два варианта. Первый — оставить вызов: он требует минимум изменений, но сохраняет неопределённое поведение и может сломаться после изменения оптимизации, метода или его виртуальности. Второй — явно проверять наличие устройства перед вызовом; это немного увеличивает код, зато делает контракт очевидным.
Выбран второй вариант: при отсутствии устройства диагностика пропускается или возвращает отдельный результат, а метод вызывается только для существующего объекта. Если диагностика действительно не зависит от состояния устройства, её вынесли в статическую либо свободную функцию. В результате поведение стало определённым и не зависело от режима сборки.
Вопрос: Почему невиртуальный вызов через nullptr иногда не падает немедленно?
Ответ: Для невиртуального метода компилятор может знать адрес функции напрямую и не читать память объекта перед переходом в тело метода. Поэтому машинный код иногда выполняется до тех пор, пока метод не использует this. Но на уровне стандарта корректного объекта нет, поэтому результат не гарантирован и не должен использоваться как рабочий приём.
Вопрос: Чем опаснее вызов виртуального метода через нулевой указатель?
Ответ: Для выбора переопределения обычно требуется динамический тип объекта. Реализация, как правило, читает указатель на таблицу виртуальных функций из области объекта; у nullptr такой области нет, поэтому авария может произойти до входа в тело метода. Однако принципиальный вывод тот же: вызов имеет неопределённое поведение независимо от того, произойдёт ли фактическое падение.
Вопрос: Достаточно ли внутри метода проверить this на равенство nullptr?
Ответ: Нет. До выполнения тела уже произошёл некорректный вызов нестатического метода, поэтому такая проверка не возвращает программе определённое поведение. Если нулевой указатель допустим, проверять его нужно до вызова; ещё лучше выразить отсутствие объекта через подходящий интерфейс, например ссылку там, где объект обязателен, или std::optional/умный указатель там, где он необязателен.