динамическое и статическое приведение в C++
Я совсем запутался с dynamic_cast ключевое слово в C++.
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
}
определение говорит:
The
dynamic_castключевое слово приводит датум из одного указателя или ссылки
введите другой, выполняя проверку времени выполнения, чтобы обеспечить правильность приведения
можем ли мы написать эквивалент dynamic_cast из C++ в C, чтобы я мог лучше понять вещи?
10 ответов:
вот краткое изложение на
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*. Но это приведение выполняется во время выполнения, а не во время компиляции. Поскольку это приведение во время выполнения, оно полезно, особенно в сочетании с полиморфными классами. На самом деле, в сертификационных случаях классы должны быть полиморфным для того, чтобы бросок был законным.может пойти в одном из двух направления: от основания к производному (B2D) или от производного к основанию (D2B). Это достаточно просто, чтобы увидеть, как d2b бросает будет работать во время выполнения. Либо
ptrпроисходит отTypeили его не было. В случае Д2Б динамическое приведение dynamic_castы, правила просты. Вы можете попробовать бросить что-нибудь на что-нибудь еще, и еслиptrбыл фактически выведен изType, вы получитеType*указатель обратно изdynamic_cast. В противном случае вы получите нулевой указатель.но 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-стиль castBar* bar = (Bar*)base;решительно не тип-безопасный. Как ты мог это исправить? Одним из способов было бы добавить такую функцию, как boolAreYouABar() 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 должны быть тем, что стандарт называет полиморфных типов. Чтобы быть полиморфным типом, ваш класс должен иметь хотя бы один . Если ваши классы не являются полиморфными типами, базовое производное использование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, сделает как базовые, так и 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; }
если вы не реализуете свой собственный ручной RTTI (и в обход системного), это невозможно реализовать
dynamic_castнепосредственно в коде пользовательского уровня C++.dynamic_castочень сильно привязан к системе RTTI реализации C++.но, чтобы помочь вам понять RTTI (и таким образом
dynamic_cast) больше, вы должны прочитать о иtypeidоператора. Это возвращает информацию о типе, соответствующую объекту, который у вас есть под рукой, и вы можете запросить различные (ограниченные) вещи из этих типов информационных объектов.
больше, чем код в C, я думаю, что английского определения может быть достаточно:
учитывая базу классов, из которой есть производный класс производный,
dynamic_castпреобразует базовый указатель в производный указатель тогда и только тогда, когда фактический объект, на который указывают, на самом деле является производным объектом.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. Внутренне ссылка downcasted со ссылкой наDerivedв a типобезопасный способ: понижение будет успешным только для тех случаев, когда ссылочный объект действительно является экземпляромDerived.
A
dynamic_castвыполняет проверку типа с помощью RTTI. Если он не сработает, он выдаст вам исключение (если вы дали ему ссылку) или NULL, если вы дали ему указатель.
следующее не очень близко к тому, что вы получаете от C++'S
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's a dog if(dyn_cast(Animal::Dog, animal)) { bark(animal); // we are sure the call is safe } delete animal; }
в C нет классов, поэтому невозможно написать dynamic_cast на этом языке. Структуры C не имеют методов (в результате у них нет виртуальных методов), поэтому в нем нет ничего "динамического".
нет, не легко. Компилятор присваивает уникальный идентификатор каждому классу, на эту информацию ссылается каждый экземпляр объекта, и это то, что проверяется во время выполнения, чтобы определить, является ли динамическое приведение законным. Вы можете создать стандартный базовый класс с этой информацией и операторами для выполнения проверки времени выполнения на этом базовом классе, тогда любой производный класс сообщит базовому классу о его месте в иерархии классов, и любые экземпляры этих классов будут кастируемыми во время выполнения через ваши операции.
edit
вот реализация, которая демонстрирует один метод. Я не утверждаю, что компилятор использует что-то подобное, но я думаю, что он демонстрирует концепции:
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; }
dynamic_cast использует RTTI. Это может замедлить ваше приложение, вы можете использовать модификацию Шаблона дизайна посетителя для достижения downcasting без RTTI http://arturx64.github.io/programming-world/2016/02/06/lazy-visitor.html
static_cast< Type* >(ptr)static_cast в C++ может использоваться в сценариях, где все приведение типов может быть проверено во время компиляции.
dynamic_cast< Type* >(ptr)dynamic_cast в C++ можно использовать для выполнения тип безопасный вниз литья. 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"; }
во-первых, чтобы описать динамическое приведение в терминах C, мы должны представлять классы в C. Классы с виртуальными функциями используют "VTABLE" указателей на виртуальные функции. Комментарии в C++. Не стесняйтесь переформатировать и исправить ошибки компиляции...
// class A { public: int data; virtual int GetData(){return data;} }; typedef struct A { void**vtable; int data;} A; int AGetData(A*this){ return this->data; } void * Avtable[] = { (void*)AGetData }; A * newA() { A*res = malloc(sizeof(A)); res->vtable = Avtable; return res; } // class B : public class A { public: int moredata; virtual int GetData(){return data+1;} } typedef struct B { void**vtable; int data; int moredata; } B; int BGetData(B*this){ return this->data + 1; } void * Bvtable[] = { (void*)BGetData }; B * newB() { B*res = malloc(sizeof(B)); res->vtable = Bvtable; return res; } // int temp = ptr->GetData(); int temp = ((int(*)())ptr->vtable[0])();тогда динамический бросок-это что-то вроде:
// A * ptr = new B(); A * ptr = (A*) newB(); // B * aB = dynamic_cast<B>(ptr); B * aB = ( ptr->vtable == Bvtable ? (B*) aB : (B*) 0 );
Comments