Я недавно столкнулся со следующим кодом:
class Foo
{
public:
void bar();
// .. other stuff
};
void Foo::bar()
{
if(!this) {
// .. do some stuff without accessing any data members
return;
}
// .. do normal actions using data members
}
Код компилирует, потому что в C++ методы являются просто функциями, которые неявно передаются указатель для 'этого', и 'это' может быть проверено для ПУСТОГО УКАЗАТЕЛЯ точно так же, как любой другой указатель. Очевидно, этот код является запутывающей и плохой практикой даже при том, что это не отказывает; это довольно сбивало бы с толку, чтобы ступить через код в отладчик, видеть, что Нулевой указатель собирается обратиться к методу это, и затем не посмотрите ожидаемый катастрофический отказ. Мой вопрос: это нарушает стандарт C++ для вызова SomeFooPtr->bar()
где SomeFooPtr == NULL
?
Мне приходит в голову, что это может, не потому что определяемый пользователем оператор-> возвращает указатель, что означает, что, даже если тот указатель является НУЛЕВЫМ, он определенно не был разыменован (разыменование Нулевого указателя, я уверен, рассматривается стандартом как недопустимый или неопределенный). С другой стороны, семантика необработанных указателей должна не обязательно соответствовать семантике определяемых пользователем указателей - возможно, оператор-> на них считают разыменовыванием даже при том, что компилятор не генерирует тот.
Это, вероятно, будет работать на большинстве систем, но это Неопределенное поведение. Цитируем стандарт:
5.2.5.3
Если
E1
имеет тип "указатель на класс X", то выражениеE1->E2
преобразуется в эквивалентную форму(*(E1)).E2
[...]
И:
5.2.5.1
Постфиксное выражение, за которым следует точка
.
или стрелкой->
, за которым по выбору следует ключевое словоtemplate
(14.8.1), а затем id-выражение, является постфиксным выражением. Постфиксное выражение перед точкой или стрелкой оценивается; 58) [...]58) Эта оценка происходит, даже если результат не нужен для определения значения всего постфиксного выражения, например, если id-выражение обозначает статический член.
Оценка *x
, где x
- нулевой указатель, приводит к неопределенному поведению, так что вы явно имеете дело с UB, еще до ввода функции.
Этот тест не работает, даже если разыменование не было UB. Он ломается, когда в игру вступают this-настройки для множественного наследования:
#include <stdio.h>
class B
{
int value;
public:
void foo()
{
if (!this)
printf("this==0\n");
else
printf("safe\n");
}
};
class A { public: int anotherValue; };
class Z : public A,public B {};
int main()
{
Z *z=0;
z->foo();
}
выводит здесь «безопасно».
Это (теперь все вместе) неопределенное поведение . Однако для многих компиляторов это будет работать с дополнительным ограничением, заключающимся в том, что метод не должен быть виртуальным.
Это УБ. Хороший способ вызвать сбой - использовать его в качестве базового класса производного класса, который использует множественное наследование. YMMV.
Неважно, законно ли это, это сбивает с толку читателя. В реализации, где этот код работает, доступ к vtable осуществляется по типу, а не по объекту.
Более того, я предполагаю, что этот код был вставлен для прикрытия сбоя конструктора, который скрыл бы множество проблем в других местах. Сбой конструктора должен быть обработан правильно, а не с помощью мерзкой уловки, как в примере.