C ++中的dynamic_cast和static_cast


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 ++的工作方式),那么Lippman的“ Inside the C ++ Object Model”就是一本好书(对于如此技术性的东西也很容易阅读)。同样,Stroustrup的“ C ++的设计和演进”和“ C ++编程语言”这两本书都是不错的资源,但是Lippman的书致力于C ++在幕后的工作方式。
Michael Burr 2010年

该行中的注释是什么B* b2 = dynamic_cast<B*> (ap) // 'b'意思?b2 is pointer to b或者是什么?
LRDPRDX

@BogdanSikach那是什么问题?它只是意味着ap现在是B类的一种

Answers:


282

这是一个总结static_cast<>dynamic_cast<>尤其是与指针有关的。这只是一个101级的摘要,并不涵盖所有复杂性。

static_cast <类型*>(ptr)

ptr会将指针带入并尝试将其安全地强制转换为type的指针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的指针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()将返回哪种对象,因此C样式Bar* bar = (Bar*)base;强制转换绝对不是类型安全的。您该如何解决?一种方法是AreYouABar() const = 0;在基类中添加bool 之类的函数trueBar然后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必须是Standard称为多态类型的类型。为了成为多态类型,您的类必须至少具有一个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 Base* base = new Base;
MuneshSingh

3
@munesh因为base不是Foo。一个Base指针可以指向一个Foo,但它仍然是一个Foo,所以一个动态转换将工作。如果这样做Base* base = new Basebase则是a Base而不是a Foo,因此您不能动态地将其强制转换为a Foo
Yay295 '17

20

除非您实现自己的手动RTTI(并绕过系统的RTTI),否则不可能dynamic_cast直接在C ++用户级代码中实现。dynamic_cast与C ++实现的RTTI系统紧密相关。

但是,为了帮助您dynamic_cast更好地理解RTTI(并进而),您应该阅读<typeinfo>标题和typeid运算符。这将返回与您手边的对象相对应的类型信息,您可以从这些类型信息对象中查询各种(有限的)事物。


我会向您指出Wikipedia,但其有关RTTI的文章dynamic_cast非常ski琐。:-P自己玩,直到掌握它为止。:-)
Chris Jester-Young 2010年

10

除了用C语言编写代码外,我认为英语定义就足够了:

给定一个具有派生类Derived dynamic_cast的Base 类,则当且仅当所指向的实际对象实际上是Derived对象时,才会将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:仅当所引用的对象确实是的实例的情况下,下转换才能成功Derived


2
我认为最好澄清一下,如果类仅是多态的,即至少基类至少具有虚拟方法,则以上共享的示例将基于假设起作用。
irsis

1
这将失败,因为类不是多态类型。
username_4567

4

dynamic_cast在类型检查方面,以下内容与从C ++获得的内容并不十分接近,但也许可以帮助您更好地理解其目的:

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”。注释是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
最初的问题是“我们可以在C语言中编写等效的C ++ dynamic_cast吗”。
大卫·雷纳

1

C中没有类,因此用该语言编写dynamic_cast是不可能的。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)

在可以在编译时验证所有类型转换的情况下,可以使用C ++中的static_cast 。

dynamic_cast< Type* >(ptr)

C ++中的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";
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.