dynamic_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 Что это за вопрос? Это просто означает, что

Ответы:

283

Вот краткое изложение static_cast<>и, в dynamic_cast<>частности, как они относятся к указателям. Это всего лишь 101-кратное изложение, оно не охватывает все тонкости.

static_cast <Type *> (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 <Type *> (ptr)

Это снова пытается принять указатель ptrи безопасно привести его к указателю типа Type*. Но это приведение выполняется во время выполнения, а не во время компиляции. Поскольку это приведение во время выполнения, оно полезно, особенно в сочетании с полиморфными классами. Фактически, в некоторых случаях классы должны быть полиморфными, чтобы состав был законным.

Приведения могут идти в одном из двух направлений: от основания к производному (B2D) или от производного к базе (D2B). Достаточно просто увидеть, как будет выполняться приведение D2B во время выполнения. Либо ptrбыло получено из, Typeлибо нет. В случае D2B 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 Bar* bar = (Bar*)base;определенно небезопасно. Как ты мог это исправить? Одним из способов было бы добавить функцию типа 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
Почему компилятор жалуется на это в первую очередь? и разве когда мы предоставляем только виртуальный доктор только для базы?
Рика
5
Следует отметить, что если вы это сделаете Base* base = new Base;, dynamic_cast<Foo*>(base)будет NULL.
Yay295
2
@ Coderx7 dynamic_cast нуждается в информации о типах времени выполнения (RTTI), которая доступна только для полиморфных классов, то есть классов с хотя бы одним виртуальным методом.
Elvorfirilmathredia
@ Yay295 Почему dynamic_cast<Foo*>(base)в случае Base* base = new Base;?
MuneshSingh
3
@munesh Потому что baseэто не так Foo. BaseУказатель может указывать на Foo, но это по - прежнему Foo, так что динамическое приведение будет работать. Если вы это делаете Base* base = new Base, baseэто a Base, а не a Foo, поэтому вы не можете динамически привести его к a Foo.
Yay295
20

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

Но, чтобы помочь вам лучше понять RTTI (и, следовательно, dynamic_cast), вы должны прочитать <typeinfo>заголовок и typeidоператора. Это возвращает информацию о типе, соответствующую объекту, который у вас есть, и вы можете запрашивать различные (ограниченные) вещи из этих объектов информации о типе.

Крис Шут-Янг
источник
Я бы указал на Википедию, но ее статьи на RTTI и dynamic_castочень скудны. :-P Просто играй с этим сам, пока не освоишься. :-)
Крис Шут-Янг
10

Больше, чем код на C, я думаю, что английского определения может быть достаточно:

При наличии класса 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. Внутренне ссылка понижается до ссылки безопасным для 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;
}
Manuel
источник
3

A dynamic_castвыполняет проверку типа с использованием RTTI . Если он потерпит неудачу, он выдаст вам исключение (если вы дали ему ссылку) или NULL, если вы дали ему указатель.

f4.
источник
2

Во-первых, чтобы описать динамическое приведение в терминах 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 );
Дэвид Рейна
источник
1
Первоначальный вопрос был «Можем ли мы написать эквивалент dynamic_cast из C ++ в C».
Дэвид Рэйна
1

В C нет классов, поэтому невозможно написать dynamic_cast на этом языке. Структуры C не имеют методов (в результате у них нет виртуальных методов), поэтому в них нет ничего «динамического».

a1ex07
источник
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)

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";
}
Йогиш ХТ
источник