1. Это не нарезка объектов:
base *b = new derived;
Это присвоение указателя типа base
экземпляру derived
.
Один (очень распространенный) пример этого в действии - обратные вызовы. Учтите это:
class Observer
{
public:
virtual void OnEvent() = 0;
};
class Subject
{
public:
Subject() : observer(0) {}
void SetObserver(Observer* o) { observer = o; }
void FireEvent() { if(observer != 0) observer->OnEvent(); }
private:
Observer* observer;
};
class MyObserver : public Observer
{
public:
void OnEvent() { ::printf("Hi there!\n"); }
};
int main()
{
Subject s;
MyObserver o;
s.SetObserver(&o);
s.FireEvent();
return 0;
}
Это должен быть ожидаемый результат:
Привет!
Обратите внимание, что здесь происходит. Мы передаем указатель на экземпляр от MyObserver
до SetObserver()
, хотя функция принимает только указатели типа Observer
. Это работает, потому что MyObserver
(публично) происходит от Observer
. В этом случае мы говорим, что MyObserver
является Observer
.
Тип Observer
определяет чисто виртуальную функцию (=0
означает, что функция является чистой; она должна быть реализована производными классами). Ключевое слово virtual
сообщает компилятору, что вызов функции должен привести к выполнению самой производной функции. Функция OnEvent()
в MyObserver
является наиболее производной, поэтому эта версия вызывается, хотя мы вызываем OnEvent()
для указателя типа Observer
.
Мы сталкиваемся с проблемой выполнения всего этого, потому что в этом коде Subject
не нужно знать точный тип своего наблюдателя - наблюдатели просто должны получить из Observer
, а экземпляр Subject
вызовет реализация самого производного типа OnEvent()
. Это позволяет отделить код - Subject
не зависит от MyObserver
, а MyObserver
не зависит от Subject
.
2. Нет ничего плохого в приведении указателя от базового к производному типу per se . Следующее является законным и гарантированно работает:
class Base {};
class Derived : public Base {};
int main()
{
Derived d;
Base* bp = &d;
Derived* dp = static_cast<Derived*>(bp);
return 0;
}
Однако строка перед оператором return в этом фрагменте не определена:
class Base {};
class Derived1 : public Base {};
class Derived2 : public Base {};
int main()
{
Derived1 d1;
Base* bp = &d1;
Derived2* d2p = static_cast<Derived2*>(bp); // WTF?!
return 0;
}
Значение указателя d2p
не имеет смысла, и попытка доступа к чему-либо в нем, безусловно, вызовет сбой, потому что указатель bp
на самом деле не указывает на экземпляр Derived2
, он указывает на Derived1
экземпляр. Компиляторы не могут поймать это во время компиляции, потому что оба Derived1
и Derived2
наследуются от Base
, поэтому приведение успешно компилируется. Это главная опасность при приведении от базового к производному типу - до времени выполнения вы не узнаете, действительно ли приведение даст значимые результаты.
Конечно, если вы не используете dynamic_cast<>()
, но приведение влечет штраф за время выполнения. static_cast<>()
включает максимум арифметику указателя. reinterpret_cast<>()
заставляет указатель принимать другой (потенциально не связанный) тип без выполнения какой-либо арифметики указателя. Это делает reinterpret_cast<>()
одним из наиболее опасных приведений и должно использоваться только при необходимости, особенно если static_cast<>()
может выполнить работу.
3. Примите во внимание следующее:
class Base
{
public:
void Foobar() { ::printf("In Base!\n"); }
};
class Derived : public Base
{
public:
void Foobar() { ::printf("In Derived!\n"); }
};
int main()
{
Derived d;
Derived* dp = &d;
Base* bp = dp;
dp->Foobar();
bp->Foobar();
return 0;
}
Если функция Foobar()
не виртуальная, вы получите такой вывод:
In Derived!
In Base!
В противном случае, если функция Foobar()
является виртуальной, вы получите следующий вывод:
In Derived!
In Derived!
Чтобы гарантировать, что вызов виртуальной функции Foobar()
вызывает базовую реализацию через базовый указатель, необходимо использовать оператор разрешения области действия:
// Prints "In Base!", even if bp actually points
// to an instance of Derived overriding Foobar().
bp->Base::Foobar();