Вы рассуждаете о том, что происходит на практике. Неопределенное поведение позволяет делать то, что вы ожидаете ... но это не гарантируется.
Для нестатического случая это просто доказать, используя правило, найденное в [class.mfct.non-static]
:
Если нестатическая функция-член класса X
вызывается для объекта, который не относится к типу X
или к типу, производному от X
, поведение не определено.
Обратите внимание, что не учитывается, обращается ли нестатическая функция-член к *this
. Просто требуется, чтобы объект имел правильный динамический тип, а *(Foo*)nullptr
определенно не имеет.
В частности, даже на платформах, которые используют описанную вами реализацию, вызов
fooObj->func();
конвертируется в
__assume(fooObj); Foo_func(fooObj);
и является нестабильным при оптимизации.
Вот пример, который будет работать вопреки вашим ожиданиям:
int main()
{
Foo* fooObj = nullptr;
fooObj->func();
if (fooObj) {
fooObj->member = 5; // This will cause a read access violation!
}
}
В реальных системах это может привести к нарушению прав доступа к прокомментированной строке, потому что компилятор использовал тот факт, что fooObj
не может быть нулевым в fooObj->func()
, чтобы исключить if
тест, следующий за ним.
Не делайте UB вещей, даже если вы думаете, что знаете, что делает ваша платформа. Нестабильность оптимизации реальна.
Кроме того, Стандарт еще более строг, чем вы думаете. Это также вызовет UB:
struct Foo
{
int member;
void func() { std::cout << "hello";}
static void s_func() { std::cout << "greetings";}
};
int main()
{
Foo* fooObj = nullptr;
fooObj->s_func(); // well-formed call to static member,
// but unlike Foo::s_func(), it requires *fooObj to be a valid object of type Foo
}
Соответствующие части Стандарта находятся в [expr.ref]
:
Выражение E1->E2
преобразуется в эквивалентную форму (*(E1)).E2
и сопровождающая сноска
Если вычисляется выражение доступа к члену класса, оценка подвыражения происходит, даже если результат не нужен для определения значения всего постфиксного выражения, например, если id-expression обозначает статический член.
Это означает, что рассматриваемый код определенно оценивает (*fooObj)
, пытаясь создать ссылку на несуществующий объект. Было несколько предложений сделать это разрешенным и запретить только преобразование lvalue-> rvalue для такой ссылки, но до сих пор они были отклонены; даже формирование ссылки является незаконным во всех версиях Стандарта на сегодняшний день.
person
Ben Voigt
schedule
10.03.2018