سؤال

أنا في حيرة من أمري مع 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";
}
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top