динамічний_cast та static_cast у C ++


155

Я дуже плутаю 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_castC ++ в C, щоб я міг краще зрозуміти речі?


1
Якщо ви хочете отримати гарне уявлення про те, як dynamic_cast<>працює за лаштунками (або скільки C ++ працює), гарною книгою (що також досить легко читати для чогось такого технічного) є "Внутрішня об'єктна модель C ++" Ліппмена. Також книги "Структура та еволюція C ++" та "Мова програмування на C ++" Струструпа є хорошими ресурсами, але книга Ліппмана присвячена тому, як C ++ працює "за кадром".
Майкл Берр

Що означає коментар у рядку B* b2 = dynamic_cast<B*> (ap) // 'b'? b2 is pointer to bабо що?
LRDPRDX

@BogdanSikach Яке питання це? Це просто означає, що ap зараз тип B класу

Відповіді:


282

Ось пробіг на 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;
}

динамічний_кас <Тип *> (ptr)

Це знову намагається взяти вказівник ptrі безпечно віддати його до вказівника типу Type*. Але цей виступ виконується під час виконання, а не час компіляції. Оскільки це кастинг під час виконання, він корисний особливо в поєднанні з поліморфними класами. Насправді в сертифікованих випадках класи повинні бути поліморфними, щоб акторський склад був законним.

У ролях можуть йти в одному з двох напрямків: від базового до похідного (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()повернеться, тому актерський стиль у Bar* bar = (Bar*)base;форматі C , очевидно, не є безпечним для типу. Як ви могли це виправити? Одним із способів було б додати таку функцію, як bool, AreYouABar() const = 0;до базового класу та повернутись trueіз Barта falseз Foo. Але є й інший спосіб: use 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;
}

9
Чому компілятор скаржиться на це в першу чергу? а чи ні, коли ми надаємо лише віртуальний Dctor тільки для бази?
Rika

5
Слід зазначити, що якщо ви зробите Base* base = new Base;, так dynamic_cast<Foo*>(base)і буде NULL.
Yay295

2
@ Coderx7 dynamic_cast потребує інформації про тип виконання (RTTI), яка доступна лише для класів, які є поліморфними, тобто класів принаймні з одним віртуальним методом.
Elvorfirilmathredia

@ Yay295 Чому значення " dynamic_cast<Foo*>(base)null" у випадку a Base* base = new Base;?
MuneshSingh

3
@munesh Тому що baseце не a Foo. BaseПокажчик може вказувати на Foo, але це по - , як і раніше Foo, так що динамічне приведення працюватиме. Якщо це так Base* base = new Base, baseце Baseне, а не Foo, тому ви не можете динамічно привласнити його Foo.
Yay295

20

Якщо ви не реалізуєте свій власний ручний RTTI (і обхід системного), реалізувати dynamic_castбезпосередньо в коді рівня C ++ неможливо .dynamic_castдуже пов'язаний із системою RTTI впровадження C ++.

Але, щоб зрозуміти RTTI (і, таким чином dynamic_cast) більше, вам слід прочитати на <typeinfo>заголовку та typeidоператорі. Це повертає інформацію про тип, що відповідає об'єкту, який у вас є, і ви можете запитувати різні (обмежені) речі з цих інформаційних об'єктів типу.


Я хотів би вказати вам на Вікіпедію, але її статті про RTTI і dynamic_castдуже скупі. :-P Просто пограйте з ним самостійно, поки не отримаєте повісити його. :-)
Кріс Єстер-Янг

10

Більше, ніж код у С, я думаю, що англійського визначення могло бути достатньо:

Даний клас Base, з якого є похідний клас Derived, dynamic_castперетворить вказівник Base в Похідний вказівник тоді і лише тоді, коли фактичний об'єкт, на який вказував, насправді є похідним об'єктом.

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в типизированном образі: опущений будуть успішним тільки в тих випадках , коли посилання об'єкт дійсно є екземпляр Derived.


2
Я думаю, що краще уточнити, що наведені вище приклади працюватимуть на основі припущень, якщо класи лише поліморфні, тобто хоча б базовий клас має принаймні віртуальний метод.
irsis

1
Це не вдасться, оскільки класи не є поліморфними.
username_4567

4

Далі не дуже близьке до того, що ви отримуєте від 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;
}

3

A dynamic_castвиконує перевірку типу за допомогою RTTI . Якщо це не вдасться, він викине вам виняток (якщо ви даєте йому посилання) або NULL, якщо ви дасте йому покажчик.


2

По-перше, для опису динамічного відтворення в термінах C ми повинні представляти класи у C. Класи з віртуальними функціями використовують "VTABLE" покажчиків на віртуальні функції. Коментарі - це С ++. Не соромтеся переформатувати та виправити помилки компіляції ...

// 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 );

1
Початкове запитання було "Чи можемо ми записати еквівалент динамічної передачі С ++ у С".
Девід Рейна

1

Заняття в C немає, тому неможливо написати динамический_кастування на цій мові. С-структури не мають методів (як результат, у них немає віртуальних методів), тому в цьому немає нічого "динамічного".


1

Ні, не легко. Компілятор присвоює унікальний ідентичність кожному класу, на цю інформацію посилається кожен екземпляр об'єкта, і саме це перевіряється під час виконання, щоб визначити, чи є динамічний склад законним. Ви можете створити стандартний базовий клас з цією інформацією та операторів, щоб зробити перевірку виконання цього базового класу, тоді будь-який похідний клас повідомив би базовий клас про своє місце в ієрархії класів, і будь-які екземпляри цих класів були б можливими для виконання через ваші операції.

редагувати

Ось реалізація, яка демонструє одну техніку. Я не стверджую, що компілятор використовує щось подібне, але думаю, що це демонструє такі поняття:

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;
}


0

static_cast< Type* >(ptr)

static_cast в C ++ може бути використаний у сценаріях, коли кастинг всіх типів може бути перевірений під час компіляції .

dynamic_cast< Type* >(ptr)

Динамічний_каст в C ++ може використовуватися для виконання типового лиття вниз . dynam_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";
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.