Dynamic_cast и static_cast в С++
Я довольно запутался с ключевым словом dynamic_cast
в С++.
struct A {
virtual void f() { }
};
struct B : public A { };
struct C { };
void f () {
A a;
B b;
A* ap = &b;
B* b1 = dynamic_cast<B*> (&a); // NULL, because 'a' is not a 'B'
B* b2 = dynamic_cast<B*> (ap); // 'b'
C* c = dynamic_cast<C*> (ap); // NULL.
A& ar = dynamic_cast<A&> (*ap); // Ok.
B& br = dynamic_cast<B&> (*ap); // Ok.
C& cr = dynamic_cast<C&> (*ap); // std::bad_cast
}
в определении говорится:
Ключевое слово dynamic_cast
выполняет привязку данных из одного указателя или ссылки тип к другому, выполняя проверку времени выполнения, чтобы гарантировать достоверность литых
Можно ли написать эквивалент dynamic_cast
С++ в C, чтобы я мог лучше понять вещи?
Ответы
Ответ 1
Здесь приведено краткое описание static_cast<>
и dynamic_cast<>
, поскольку они относятся к указателям. Это всего лишь 101-уровневое изложение, оно не охватывает все тонкости.
static_cast < Тип * > (ptr)
Это берет указатель в ptr
и пытается безопасно применить его к указателю типа Type*
. Это действие выполняется во время компиляции. Он будет выполнять только приведение, если типы типов связаны. Если типы не связаны, вы получите ошибку компилятора. Например:
class B {};
class D : public B {};
class X {};
int main()
{
D* d = new D;
B* b = static_cast<B*>(d); // this works
X* x = static_cast<X*>(d); // ERROR - Won't compile
return 0;
}
dynamic_cast < Тип * > (ptr)
Это снова пытается взять указатель в ptr
и безопасно применить его к указателю типа Type*
. Но это приведение выполняется во время выполнения, а не во время компиляции. Поскольку это время выполнения, оно полезно, особенно в сочетании с полиморфными классами. Фактически, в случаях certian классы должны быть полиморфными, чтобы акты были законными.
Casts может идти в одном из двух направлений: от базового до производного (B2D) или от производного до базы (D2B). Это достаточно просто, чтобы увидеть, как отбрасывание D2B будет работать во время выполнения. Либо ptr
был получен из Type
, либо не был. В случае D2B dynamic_cast < > s правила просты. Вы можете попробовать что-то сделать что-нибудь еще, и если ptr
был фактически получен из Type
, вы получите указатель Type*
из dynamic_cast
. В противном случае вы получите указатель NULL.
Но броски B2D немного сложнее. Рассмотрим следующий код:
#include <iostream>
using namespace std;
class Base
{
public:
virtual void DoIt() = 0; // pure virtual
virtual ~Base() {};
};
class Foo : public Base
{
public:
virtual void DoIt() { cout << "Foo"; };
void FooIt() { cout << "Fooing It..."; }
};
class Bar : public Base
{
public :
virtual void DoIt() { cout << "Bar"; }
void BarIt() { cout << "baring It..."; }
};
Base* CreateRandom()
{
if( (rand()%2) == 0 )
return new Foo;
else
return new Bar;
}
int main()
{
for( int n = 0; n < 10; ++n )
{
Base* base = CreateRandom();
base->DoIt();
Bar* bar = (Bar*)base;
bar->BarIt();
}
return 0;
}
main()
не может определить, какой объект CreateRandom()
вернется, поэтому листинг C-стиля Bar* bar = (Bar*)base;
явно не безопасен для типов. Как вы могли это исправить? Один из способов - добавить в базовый класс функцию bool AreYouABar() const = 0;
и вернуть true
из Bar
и false
из Foo
. Но есть и другой способ: используйте dynamic_cast<>
:
int main()
{
for( int n = 0; n < 10; ++n )
{
Base* base = CreateRandom();
base->DoIt();
Bar* bar = dynamic_cast<Bar*>(base);
Foo* foo = dynamic_cast<Foo*>(base);
if( bar )
bar->BarIt();
if( foo )
foo->FooIt();
}
return 0;
}
Задания выполняются во время выполнения и работают путем запроса объекта (не нужно беспокоиться о том, как сейчас), спрашивая его, является ли он типом, который мы ищем. Если это так, dynamic_cast<Type*>
возвращает указатель; иначе он возвращает NULL.
Чтобы эта отливка от базового к другому работала с использованием dynamic_cast<>
, Base, Foo и Bar должны быть тем, что Стандарт вызывает полиморфные типы. Чтобы быть полиморфным типом, ваш класс должен иметь хотя бы одну функцию virtual
. Если ваши классы не являются полиморфными типами, использование dynamic_cast
базы-производным не будет компилироваться. Пример:
class Base {};
class Der : public Base {};
int main()
{
Base* base = new Der;
Der* der = dynamic_cast<Der*>(base); // ERROR - Won't compile
return 0;
}
Добавление виртуальной функции к базе, например виртуального dtor, приведет к получению как полиморфных типов Base, так и Der:
class Base
{
public:
virtual ~Base(){};
};
class Der : public Base {};
int main()
{
Base* base = new Der;
Der* der = dynamic_cast<Der*>(base); // OK
return 0;
}
Ответ 2
Если вы не внедряете собственный RTTI (и обходите систему один), невозможно реализовать dynamic_cast
непосредственно в коде на уровне пользователя С++. dynamic_cast
очень сильно связан с системой RTTI для реализации на С++.
Но, чтобы помочь вам лучше понять RTTI (и, следовательно, dynamic_cast
), вы должны прочитать заголовок <typeinfo>
и оператор typeid
. Это возвращает информацию о типе, соответствующую объекту, который у вас есть, и вы можете запрашивать различные (ограниченные) вещи из этих информационных объектов типа.
Ответ 3
Больше, чем код в C, я думаю, что может быть достаточно английского определения:
Учитывая класс Base, в котором существует производный класс Derived, dynamic_cast
преобразует указатель Base в Derived указатель тогда и только тогда, когда фактический объект, на который указывает объект, фактически является производным объектом.
class Base { virtual ~Base() {} };
class Derived : public Base {};
class Derived2 : public Base {};
class ReDerived : public Derived {};
void test( Base & base )
{
dynamic_cast<Derived&>(base);
}
int main() {
Base b;
Derived d;
Derived2 d2;
ReDerived rd;
test( b ); // throw: b is not a Derived object
test( d ); // ok
test( d2 ); // throw: d2 is not a Derived object
test( rd ); // ok: rd is a ReDerived, and thus a derived object
}
В этом примере вызов test
связывает разные объекты с ссылкой на Base
. Внутренняя ссылка ссылается на ссылку Derived
по типу: downcast будет успешным только для тех случаев, когда ссылочный объект действительно является экземпляром Derived
.
Ответ 4
A dynamic_cast
выполняет проверку типа, используя RTTI. Если он терпит неудачу, он выдаст вам исключение (если вы дали ссылку) или NULL, если вы указали ему указатель.
Ответ 5
Не похоже на то, что вы получаете от С++ dynamic_cast
в терминах проверки типов, но, возможно, это поможет вам понять его цель немного лучше:
struct Animal // Would be a base class in C++
{
enum Type { Dog, Cat };
Type type;
};
Animal * make_dog()
{
Animal * dog = new Animal;
dog->type = Animal::Dog;
return dog;
}
Animal * make_cat()
{
Animal * cat = new Animal;
cat->type = Animal::Cat;
return cat;
}
Animal * dyn_cast(AnimalType type, Animal * animal)
{
if(animal->type == type)
return animal;
return 0;
}
void bark(Animal * dog)
{
assert(dog->type == Animal::Dog);
// make "dog" bark
}
int main()
{
Animal * animal;
if(rand() % 2)
animal = make_dog();
else
animal = make_cat();
// At this point we have no idea what kind of animal we have
// so we use dyn_cast to see if it a dog
if(dyn_cast(Animal::Dog, animal))
{
bark(animal); // we are sure the call is safe
}
delete animal;
}
Ответ 6
Нет, не легко. Компилятор присваивает каждому классу уникальный идентификатор, на который ссылается каждый экземпляр объекта, и это то, что проверяется во время выполнения, чтобы определить, является ли динамический актер законным. Вы можете создать стандартный базовый класс с этой информацией, а операторы - провести проверку выполнения в этом базовом классе, тогда любой производный класс проинформирует базовый класс о своем месте в иерархии классов, и любые экземпляры этих классов будут исполняться с помощью ваши операции.
изменить
Здесь реализована реализация, демонстрирующая одну технику. Я не утверждаю, что компилятор использует что-то подобное, но я думаю, что он демонстрирует понятия:
class SafeCastableBase
{
public:
typedef long TypeID;
static TypeID s_nextTypeID;
static TypeID GetNextTypeID()
{
return s_nextTypeID++;
}
static TypeID GetTypeID()
{
return 0;
}
virtual bool CanCastTo(TypeID id)
{
if (GetTypeID() != id) { return false; }
return true;
}
template <class Target>
static Target *SafeCast(SafeCastableBase *pSource)
{
if (pSource->CanCastTo(Target::GetTypeID()))
{
return (Target*)pSource;
}
return NULL;
}
};
SafeCastableBase::TypeID SafeCastableBase::s_nextTypeID = 1;
class TypeIDInitializer
{
public:
TypeIDInitializer(SafeCastableBase::TypeID *pTypeID)
{
*pTypeID = SafeCastableBase::GetNextTypeID();
}
};
class ChildCastable : public SafeCastableBase
{
public:
static TypeID s_typeID;
static TypeID GetTypeID()
{
return s_typeID;
}
virtual bool CanCastTo(TypeID id)
{
if (GetTypeID() != id) { return SafeCastableBase::CanCastTo(id); }
return true;
}
};
SafeCastableBase::TypeID ChildCastable::s_typeID;
TypeIDInitializer ChildCastableInitializer(&ChildCastable::s_typeID);
class PeerChildCastable : public SafeCastableBase
{
public:
static TypeID s_typeID;
static TypeID GetTypeID()
{
return s_typeID;
}
virtual bool CanCastTo(TypeID id)
{
if (GetTypeID() != id) { return SafeCastableBase::CanCastTo(id); }
return true;
}
};
SafeCastableBase::TypeID PeerChildCastable::s_typeID;
TypeIDInitializer PeerChildCastableInitializer(&PeerChildCastable::s_typeID);
int _tmain(int argc, _TCHAR* argv[])
{
ChildCastable *pChild = new ChildCastable();
SafeCastableBase *pBase = new SafeCastableBase();
PeerChildCastable *pPeerChild = new PeerChildCastable();
ChildCastable *pSameChild = SafeCastableBase::SafeCast<ChildCastable>(pChild);
SafeCastableBase *pBaseToChild = SafeCastableBase::SafeCast<SafeCastableBase>(pChild);
ChildCastable *pNullDownCast = SafeCastableBase::SafeCast<ChildCastable>(pBase);
SafeCastableBase *pBaseToPeerChild = SafeCastableBase::SafeCast<SafeCastableBase>(pPeerChild);
ChildCastable *pNullCrossCast = SafeCastableBase::SafeCast<ChildCastable>(pPeerChild);
return 0;
}
Ответ 7
В C нет классов, поэтому невозможно написать dynamic_cast на этом языке. Структуры C не имеют методов (в результате у них нет виртуальных методов), поэтому в ней нет ничего "динамического".
Ответ 8
dynamic_cast использует RTTI. Это может замедлить ваше приложение, вы можете использовать модификацию шаблона дизайна посетителя для достижения downcasting без RTTI http://arturx64.github.io/programming-world/2016/02/06/lazy-visitor.html
Ответ 9
static_cast< Type* >(ptr)
static_cast в С++ может использоваться в сценариях, где все типы кастинг можно проверить во время компиляции.
dynamic_cast< Type* >(ptr)
dynamic_cast в С++ может использоваться для выполнения безопасного <безопасного > <безопасного > типа . dynamic_cast - это полиморфизм времени выполнения. Оператор dynamic_cast, который безопасно преобразует из указателя (или ссылки) в базовый тип в указатель (или ссылку) на производный тип.
например, 1:
#include <iostream>
using namespace std;
class A
{
public:
virtual void f(){cout << "A::f()" << endl;}
};
class B : public A
{
public:
void f(){cout << "B::f()" << endl;}
};
int main()
{
A a;
B b;
a.f(); // A::f()
b.f(); // B::f()
A *pA = &a;
B *pB = &b;
pA->f(); // A::f()
pB->f(); // B::f()
pA = &b;
// pB = &a; // not allowed
pB = dynamic_cast<B*>(&a); // allowed but it returns NULL
return 0;
}
Для получения дополнительной информации нажмите здесь
например, 2:
#include <iostream>
using namespace std;
class A {
public:
virtual void print()const {cout << " A\n";}
};
class B {
public:
virtual void print()const {cout << " B\n";}
};
class C: public A, public B {
public:
void print()const {cout << " C\n";}
};
int main()
{
A* a = new A;
B* b = new B;
C* c = new C;
a -> print(); b -> print(); c -> print();
b = dynamic_cast< B*>(a); //fails
if (b)
b -> print();
else
cout << "no B\n";
a = c;
a -> print(); //C prints
b = dynamic_cast< B*>(a); //succeeds
if (b)
b -> print();
else
cout << "no B\n";
}