Учитывая этот образец:
class Base
{
public:
void foo() {};
};
class Derived : public Base
{
};
int main()
{
Base b;
Derived* d = static_cast<Derived*>(&b);
d->foo();
}
У меня всего три случая: когда void foo()
:
Base
,Derived
,Base
и Derived
.Мои вопросы:
Является ли выражение доступа к члену d->foo()
неопределенным поведением во всех трех случаях?
Если выражением доступа к члену является UB, единственным обходным решением является использование dynamic_cast
?
обратите внимание, что foo
является членом как Base
, так и Derived
в вашем примере. Производные классы наследуют членов своих родителей.
A prvalue of type “pointer to cv1
B
”, whereB
is a class type, can be converted to a prvalue of type “pointer to cv2D
”, whereD
is a complete class derived fromB
,...
If the prvalue of type “pointer to cv1
B
” points to aB
that is actually a base class subobject of an object of typeD
, the resulting pointer points to the enclosing object of typeD
. Otherwise, the behavior is undefined.
Таким образом, использование static_cast
для приведения вниз допустимо только в том случае, если вы знаете (с помощью других средств), что приведение допустимо. Если вы возьмете Base
, что не есть Derived
, и используете static_cast
, чтобы притвориться, что это так, вы вызовете неопределенное поведение еще до того, как попытаетесь разыменовать указатель. Таким образом, вы можете удалить строку d->foo()
, и Все еще будет иметь неопределенное поведение. Это актерский состав плохой.
Если ваша цель — условно проверить, есть ли у вас экземпляр подкласса, то вам нужен dynamic_cast
.
static_cast<Derived*>(&b);
дает UB?
Да. Строка static_cast
дает неопределенное поведение. Вы приводите значение к типу, которому оно не принадлежит, и используете приведение, которое эффективно утверждает его безопасность без проверки.
.. а затем разыменование указателя d
дает UB, независимо от того, к какому элементу он пытается получить доступ?
Хуже, чем это. Неопределенное поведение распространяется в вашем коде вперед и назад, поэтому, пока эта строка существуют в вашей программе, каждая написанная вами функция является UB. Умный компилятор C++ может заменить всю вашу программу на cout << "Hello world"
и по-прежнему соответствовать стандарту. По сути, компиляторам разрешено предполагать, что UB не происходит, и оптимизировать для этого варианта использования, поэтому, если UB происходит, тогда ex falso quodlibet все возможно.
@SilvioMayolo UB распространяется, только если выполняется путь кода. if (false) { UB; }
не вызывает никаких плохих вещей. С другой стороны, a(); if (flag) UB;
можно законно преобразовать в if (flag) { UB; } else a();
, потому что UB
распространяется в обратном направлении, чтобы вообще предотвратить выполнение a()
, если flag
истинно.