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