Когда апкастинг незаконен в C++?
Я точно понимаю общую разницу между восходящим и нисходящим приведением типов, особенно в C++. Я знаю, что мы не всегда можем выполнить нисходящее приведение, поскольку преобразование указателя базового класса в указатель производного класса предполагает, что объект базового класса, на который указывает указатель, имеет все члены производного класса.
В начале семестра мой профессор сказал, что иногда восходящее приведение типов в C++ также может быть незаконным, но я, похоже, пропустил объяснение причины этого в своих записях и не могу вспомнить, когда это происходит.
Когда восходящее приведение типов в C++ является незаконным?
3 ответ(ов)
Если под "недопустимым" вы имеете в виду плохо сформированный код, то это действительно недопустимо, если базовый класс недоступен или происходит неоднозначность.
Он недоступен, например, когда базовый класс является приватным.
class A {}; class B : A {}; ... B b; A *pa = &b; // ОШИБКА: базовый класс недоступен
Обратите внимание, что даже в C++11 можно использовать C-style cast, чтобы "обойти" защиту доступа и выполнить формально корректное преобразование вверх.
A *pa = (A *) &b; // ОК, не reinterpret_cast, а допустимое преобразование вверх
Это использование, конечно, следует избегать.
Он неоднозначен, если ваш исходный тип содержит несколько базовых подобъектов целевого типа (через множественное наследование).
class A {}; class B : public A {}; class C : public A {}; class D : public B, public C {}; D d; A *pa = &d; // ОШИБКА: базовый класс неоднозначен
В таких случаях можно выполнить преобразование вверх, явно "пройдя" по нужному пути преобразования с промежуточными преобразованиями вверх до того момента, когда базовый класс больше не будет неоднозначен.
B* pb = &d; A* pa = pb; // ОК: указывает на подчинённый объект 'D::B::A'
Если базовый класс является неоднозначным (наследован по двум или более путям), то выполнить приведение вверх за один шаг не получится.
Если же базовый класс недоступен, то единственный способ выполнить приведение вверх — использовать приведение в стиле C. Это особый случай такого приведения, и именно оно может решить эту задачу. По сути, оно ведёт себя как static_cast
, но не ограничивается доступностью.
Стандартный текст.
C++11 §5.4/4:
” … в [приведении в стиле C] выполнение
static_cast
в следующих ситуациях допустимо, даже если базовый класс недоступен:
- указатель на объект производного типа или lvalue/rvalue производного типа может быть явно преобразован в указатель или ссылку на четко определенный базовый класс, соответственно;
- указатель на член производного типа может быть явно преобразован в указатель на член четко определенного не-виртуального базового класса;
- указатель на объект четко определенного не-виртуального базового класса, glvalue четко определенного не-виртуального базового класса или указатель на член четко определенного не-виртуального базового класса может быть явно преобразован в указатель, ссылку или указатель на член производного типа, соответственно.
Пример неоднозначности:
struct Base {};
struct M1: Base {};
struct M2: Base {};
struct Derived: M1, M2 {};
auto main() -> int
{
Derived d;
//static_cast<Base&>( d ); //! Неоднозначно
static_cast<Base&>( static_cast<M2&>( d ) ); // ОК
}
Пример недоступного базового класса, с (обычно) корректировкой адреса в привидении:
struct Base { int value; Base( int x ): value( x ) {} };
class Derived
: private Base
{
public:
virtual ~Derived() {} // Просто чтобы создать необходимость в корректировке адреса.
Derived(): Base( 42 ) {}
};
#include <iostream>
using namespace std;
auto main() -> int
{
Derived d;
Base& b = (Base&) d;
cout << "Производный класс по адресу " << &d << ", базовый класс по адресу " << &b << endl;
cout << b.value << endl;
};
Таким образом, следует помнить, что использование приведения в стиле C может помочь в ситуациях, когда доступ к базовому классу затруднён или невозможен, но с осторожностью, так как это может привести к неявным ошибкам при работе с объектами.
В C++ существуют два случая, когда приведение к базовому классу (upcasting) является недопустимым и выявляется на этапе компиляции:
- Базовый класс недоступен:
class base {};
class derived : base {};
int main() {
derived x;
base& y = x; // недопустимо, так как базовый класс недоступен.
// Решение: использование C-style приведения (как static_cast без проверки доступа)
base& y1 = (base&)x;
}
- Базовый класс не имеет однозначности:
class base {};
struct A1 : base {};
struct A2 : base {};
struct derived : A1, A2 {};
int main() {
derived x;
base& y = x; // недопустимо, так как это приводит к неоднозначности.
// Решение 1: разрешение области видимости:
base& y1 = static_cast<A1::base&>(x);
base& y2 = static_cast<A2::base&>(x);
// Решение 2: промежуточные однозначные шаги:
A1& a1 = x;
A2& a2 = x;
base& ya1 = a1;
base& ya2 = a2;
}
Эти примеры демонстрируют, как можно обойти ограничения компилятора в случае недоступности или неоднозначности базовых классов при приведении к ним.
Имеет ли ключевое слово 'mutable' какие-либо другие цели, кроме разрешения изменения члена данных в константной функции-члене?
Что такое Правило трёх?
Имеют ли круглые скобки после имени типа значение при использовании new?
Как получить значение закрытого поля из другого класса в Java?
`unsigned int` против `size_t`: когда и что использовать?