Существует причина, по которой у вас нет никакой подсказки, dynamic_cast
и static_cast
не похожи на const_cast
или reinterpret_cast
, они фактически выполняют арифметику указателей и в некоторой степени безопасны от типов.
Указатель арифметический
Чтобы проиллюстрировать это, подумайте о следующем дизайне:
struct Base1 { virtual ~Base1(); char a; };
struct Base2 { virtual ~Base2(); char b; };
struct Derived: Base1, Base2 {};
Экземпляр Derived
должен выглядеть примерно так (он основан на gcc, поскольку на самом деле он зависит от компилятора ...):
| Cell 1 | Cell 2 | Cell 3 | Cell 4 |
| vtable pointer | a | vtable pointer | b |
| Base 1 | Base 2 |
| Derived |
Теперь подумайте о работе, необходимой для кастинга:
- приведение от
Derived
к Base1
не требует дополнительной работы, они находятся по тому же физическому адресу
- приведение от
Derived
до Base2
требует сдвига указателя на 2 байта
Следовательно, необходимо знать расположение объектов в памяти, чтобы иметь возможность проводить преобразование между одним производным объектом и одним из его основания. И это известно только компилятору, информация не доступна через какой-либо API, она не стандартизирована или что-либо еще.
В коде это будет выглядеть так:
Derived derived;
Base2* b2 = reinterpret_cast<Base2>(((char*)&derived) + 2);
И это, конечно, для static_cast
.
Теперь, если бы вы могли использовать static_cast
в реализации dynamic_cast
, то вы могли бы использовать компилятор и позволить ему обрабатывать арифметику указателей для вас ... но вы все еще не ушли .
Написание dynamic_cast?
Перво-наперво, нам нужно уточнить спецификации dynamic_cast
:
dynamic_cast<Derived*>(&base);
возвращает ноль, если base
не является экземпляром Derived
.
dynamic_cast<Derived&>(base);
бросает std::bad_cast
в этом случае.
dynamic_cast<void*>(base);
возвращает адрес самого производного класса
dynamic_cast
соблюдайте спецификации доступа (public
, protected
и private
наследование)
Я не знаю о вас, но я думаю, что это будет ужасно. Использование typeid
здесь недостаточно:
struct Base { virtual ~Base(); };
struct Intermediate: Base {};
struct Derived: Base {};
void func()
{
Derived derived;
Base& base = derived;
Intermediate& inter = dynamic_cast<Intermediate&>(base); // arg
}
Проблема здесь в том, что typeid(base) == typeid(Derived) != typeid(Intermediate)
, так что вы тоже не можете на это полагаться.
Еще одна забавная вещь:
struct Base { virtual ~Base(); };
struct Derived: virtual Base {};
void func(Base& base)
{
Derived& derived = static_cast<Derived&>(base); // Fails
}
static_cast
не работает, когда задействовано виртуальное наследование ... поэтому мы столкнулись с проблемой арифметических вычислений с указателями.
Почти решение
class Object
{
public:
Object(): mMostDerived(0) {}
virtual ~Object() {}
void* GetMostDerived() const { return mMostDerived; }
template <class T>
T* dynamiccast()
{
Object const& me = *this;
return const_cast<T*>(me.dynamiccast());
}
template <class T>
T const* dynamiccast() const
{
char const* name = typeid(T).name();
derived_t::const_iterator it = mDeriveds.find(name);
if (it == mDeriveds.end()) { return 0; }
else { return reinterpret_cast<T const*>(it->second); }
}
protected:
template <class T>
void add(T* t)
{
void* address = t;
mDerived[typeid(t).name()] = address;
if (mMostDerived == 0 || mMostDerived > address) { mMostDerived= address; }
}
private:
typedef std::map < char const*, void* > derived_t;
void* mMostDerived;
derived_t mDeriveds;
};
// Purposely no doing anything to help swapping...
template <class T>
T* dynamiccast(Object* o) { return o ? o->dynamiccast<T>() : 0; }
template <class T>
T const* dynamiccast(Object const* o) { return o ? o->dynamiccast<T>() : 0; }
template <class T>
T& dynamiccast(Object& o)
{
if (T* t = o.dynamiccast<T>()) { return t; }
else { throw std::bad_cast(); }
}
template <class T>
T const& dynamiccast(Object const& o)
{
if (T const* t = o.dynamiccast<T>()) { return t; }
else { throw std::bad_cast(); }
}
Вам понадобятся некоторые мелочи в конструкторе:
class Base: public Object
{
public:
Base() { this->add(this); }
};
Итак, давайте проверим:
- классическое использование: хорошо
virtual
наследование? должно работать ... но не проверено
- с учетом спецификаторов доступа ... ARG: /
Удачи всем, кто пытается реализовать это вне компилятора, действительно: x