Dynamic_cast و static_cast في C++
-
20-09-2019 - |
سؤال
أنا في حيرة من أمري مع 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
}
التعريف يقول:
ال
dynamic_cast
الكلمة الرئيسية يلقي مسند من مؤشر أو نوع مرجعي إلى آخر ، مما يؤدي فحص وقت التشغيل لضمان صحة الممثلين
هل يمكننا أن نكتب ما يعادلها dynamic_cast
من C++ في C حتى أتمكن من فهم الأشياء بشكل أفضل؟
المحلول
وهنا المتهدمة على 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 الطبقات <م> يجب م> أن يكون متعدد الأشكال من أجل المدلى بها لتكون قانونية.
ويلقي يمكن أن يذهب في واحد من اتجاهين: من قاعدة المستمدة منها (B2D) أو من المشتقة إلى قاعدة (D2B). انها كافية بسيطة لنرى كيف يلقي D2B ستعمل في وقت التشغيل. وقد اشتق إما ptr
من Type
أو لم يكن. في حالة D2B dynamic_cast <> الصورة، وقواعد بسيطة. يمكنك محاولة يلقي أي شيء إلى أي شيء آخر، وإذا كان 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()
الكائن سيعود، وبالتالي فإن يلقي Bar* bar = (Bar*)base;
على غرار C هو بالتأكيد لم تكتب آمنة. كيف يمكن إصلاح هذا؟ وسيكون أحد السبل لإضافة وظيفة مثل 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<>
، يجب أن تكون قاعدة، فو وبار ما يسميه القياسية <م> أنواع متعددة الأشكال م>. من أجل أن يكون نوع متعدد الأشكال، يجب أن يكون صفك وظيفة 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 الظاهري، وجعل كل قاعدة ودير أنواع متعددة الأشكال:
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
كثيرا في C ++ نظام RTTI تنفيذ و.
ولكن، لمساعدتك على فهم RTTI (وبالتالي dynamic_cast
) أكثر من ذلك، يجب عليك أن تقرأ على رأس <typeinfo>
، والمشغل 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
بطريقة typesafe: ومسبل ستنجح فقط عن الحالات التي يكون فيها الكائن المشار إليه هو في الواقع مثيل Derived
وفيما يلي ليست في الحقيقة قريبة إلى ما تحصل عليه من C ++ الصورة 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;
}
وA dynamic_cast
ينفذ نوع التحقق باستخدام RTTI . إذا فشل فإنه سوف يرميك استثناء (إذا أعطاه إشارة) أو NULL إذا أعطاه المؤشر.
أولا، لوصف يلقي ديناميكية من حيث 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 );
وهناك لا توجد فصول دراسية في C، لذلك فمن المستحيل أن يكتب dynamic_cast في تلك اللغة. هياكل C لا تملك أساليب (نتيجة لذلك، لم يكن لديهم أساليب الظاهرية)، لذلك لا يوجد شيء "ديناميكية" في ذلك.
لا، ليس بسهولة.يقوم المترجم بتعيين هوية فريدة لكل فئة، ويتم الرجوع إلى هذه المعلومات بواسطة كل مثيل كائن، وهذا ما يتم فحصه في وقت التشغيل لتحديد ما إذا كان التمثيل الديناميكي قانونيًا.يمكنك إنشاء فئة أساسية قياسية باستخدام هذه المعلومات وعوامل التشغيل لإجراء فحص وقت التشغيل على تلك الفئة الأساسية، ثم تقوم أي فئة مشتقة بإبلاغ الفئة الأساسية بمكانها في التسلسل الهرمي للفئة وأي مثيلات لهذه الفئات ستكون قابلة للتشغيل في وقت التشغيل عبر عملياتك.
يحرر
فيما يلي تطبيق يوضح تقنية واحدة.أنا لا أدعي أن المترجم يستخدم أي شيء مثل هذا، ولكن أعتقد أنه يوضح المفاهيم:
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 <لأ href = "http://arturx64.github.io/programming-world/2016/02/06/lazy-visitor. أتش تي أم أل "يختلط =" نوفولو "> 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 هو تعدد الأشكال في وقت التشغيل.عامل التشغيل الديناميكي، الذي يتحول بأمان من المؤشر (أو المرجع) إلى النوع الأساسي إلى المؤشر (أو المرجع) إلى النوع المشتق.
على سبيل المثال 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";
}