Комментарии 18
Они и сами путаются и стандарт всё дальше запутывают. Функция вызова невиртуального метода для указателя null нужна для реализации проверок в операторах преобразования типа, перегруженных операторов (при работе с коллекциями, например) и т.п. При нынешних формулировках этого сделать нельзя. Но можно при этом можно извратиться и получить похожий результат с дружественными функциями. Почему — загадка великая есмь.
При том, что вызов невиртуального метода для NULL — совершенно нормальная с точки зрения компилятора вещь без всякого криминала — ну будет неявный параметр (this) равен NULL и что? Код внутри метода может корректно обрабатывать такие ситуации.
При том, что вызов невиртуального метода для NULL — совершенно нормальная с точки зрения компилятора вещь без всякого криминала — ну будет неявный параметр (this) равен NULL и что? Код внутри метода может корректно обрабатывать такие ситуации.
При том, что вызов невиртуального метода для NULL — совершенно нормальная с точки зрения компилятора вещь без всякого криминала — ну будет неявный параметр (this) равен NULL и что? Код внутри метода может корректно обрабатывать такие ситуации.
Вызов метода от nullptr это UB. Точнее, UB — это проверять this на nullptr. В теории, можно написать метод, который this не проверяет и не использует, как в примере в статье — и это будет работать. Но зачем вам метод класса который не использует this, сделайте его static.
Теперь, собственно, почему проверка this это UB. Рассмотрим пример
struct A
{
static inline int globalInt = 0;
int localInt = 0;
int foo() { if (!this) return globalInt; else return localInt; }
// сеттер не нужен для понимания идеи но пусть будет для полноты
void setFoo(int i) { if (!this) globalInt = i; else localInt = i; }
};
вот так всё работает
A *a = nullptr;
int i = a->foo();
Усложним пример, добавим немного наследования
class B { int i{0}; int j{0}; };
class С : public B, public A {};
И попробуем сделать тоже самое
C *c = nullptr;
int i = c->foo();
Упс, теперь this для базового класса A — это nullptr + sizeof(B). Немного не то, что ожидалось
Вызов метода от nullptr это UB. Точнее, UB — это проверять this на nullptr.
По стандарту, вызов метода от
nullptr
— это UB, а сравнение this
с nullptr
легально, всегда возвращает false
, и на него уже давно ругаются компиляторы как на бессмысленный код.Да, я неправильно выразился, должно быть «Вызов метода от nullptr это UB. А также, UB — это проверять this на nullptr.» Никогда особо не умел связно писать тексты=(
На сколько я знаю, не возвращают они false всегда, т.к. я видел всякий legacy-код в значимых количествах, который допускает вызов методов с this=nullptr. Ругаются ворнингами — да, но сравнение отрабатывают честно, т.к. иначе ломается legacy.
Потому и возмущаюсь, что формально формулировки новых редакций стандарта ломают legacy на ровном месте, а уже разработчикам компиляторов приходится костыли подставлять. И то же время в стандарте разводят реальный ад в попытках сохранить легаси там, где его можно было смело поломать, а 0.00001% поломанного некорректного кода заставить добавив какую-нибудь прагму (привет безумию с конструкторами, где половина безумия не нужна, а вторая половина ещё и умудряется ломать старый код).
Потому и возмущаюсь, что формально формулировки новых редакций стандарта ломают legacy на ровном месте, а уже разработчикам компиляторов приходится костыли подставлять. И то же время в стандарте разводят реальный ад в попытках сохранить легаси там, где его можно было смело поломать, а 0.00001% поломанного некорректного кода заставить добавив какую-нибудь прагму (привет безумию с конструкторами, где половина безумия не нужна, а вторая половина ещё и умудряется ломать старый код).
Я такое только у MFC помню, и кажется MS специально поддерживала this=0 в компиляторе, чтобы старый код работал. Я не знаю, поддерживается ли это до сих пор последними версиями студии, но gcc точно убирает все проверки `if (this)` начиная еще с 6-й версии (хотел написать «недавно начала», но потом понял, что уже несколько лет прошло с даты релиза).
Нет. Только что проверил — «if (this==nullptr)» отрабатывает корректно в GCC 7.1.1
Уверен, что и в девятой будет работать. Там реально есть популярный легаси код, который с этим работает.
Уверен, что и в девятой будет работать. Там реально есть популярный легаси код, который с этим работает.
Или это баг в оптимизаторе компилятора или я чего-то не понимаю.
На GCC 7.1.1 вот это работает правильно:
На GCC 7.1.1 вот это работает правильно:
#include <iostream>
using namespace std;
class A {
public:
void non_virtual_mem_fn() {
if (this==nullptr)
cout << "NULL" << endl;
else
cout << "NOT NULL" << endl;
}
};
int main() {
A* ptr_null = nullptr;
ptr_null->non_virtual_mem_fn();
A* ptr_not_null = new A();
ptr_not_null->non_virtual_mem_fn();
delete(ptr_not_null);
return 0;
}
Ага, опция -O1/-O2 меняет поведение компилятора на противоположное.
Проверил на godbolt на минимальном примере, проверка выполняется только при отключенной оптимизации (
godbolt.org/z/4qdbzq
-O0
):godbolt.org/z/4qdbzq
При линейном наследовании объекты «растут» в сторону увеличения адресов, и указатель на объект всегда указывает на самого первого предка, а потомки «знают», что их данные располагаются в памяти после данных предка.
И, да, только в случае множественного наследования может быть преобразование указателя с интересными результатами.
Но множественное наследование и виртуальные функции это уже частные случаи, где UB действительно неизбежно. Зачем было распространять его на линейное наследование и невиртуальные функции?
И именно за такой идиотизм с UB на ровном месте авторам стандарта и нужно гореть в аду.
И, да, только в случае множественного наследования может быть преобразование указателя с интересными результатами.
Но множественное наследование и виртуальные функции это уже частные случаи, где UB действительно неизбежно. Зачем было распространять его на линейное наследование и невиртуальные функции?
И именно за такой идиотизм с UB на ровном месте авторам стандарта и нужно гореть в аду.
Чем дальше в лес, тем меньше света…
Диву даёшься, как чтение стандарта C++ всё дальше превращается в гуманитарную дисциплину с анализом значений синонимов и мнений авторитетных толкователей.
Зарегистрируйтесь на Хабре, чтобы оставить комментарий
Как можно и как нельзя использовать нулевой указатель в С++