C++中的dynamic_cast和static_cast

Vij*_*jay 149 c++ dynamic-cast

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
}
Run Code Online (Sandbox Code Playgroud)

定义说:

dynamic_cast关键字从一个指针或引用类型到另一个蒙上了基准,执行运行时检查以确保铸造的有效性

我们可以dynamic_cast在C中编写一个等效的C++,以便我能更好地理解事物吗?

Joh*_*ing 263

这是一个简介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;
}
Run Code Online (Sandbox Code Playgroud)

dynamic_cast <Type*>(ptr)

这再次尝试将指针插入ptr并安全地将其转换为类型的指针Type*.但是这个演员表是在运行时执行的,而不是编译时.因为这是一个运行时转换,所以特别是在与多态类组合时非常有用.实际上,在certian情况下,类必须是多态的,以使转换合法.

演员可以进入两个方向之一:从基础到派生(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;
}
Run Code Online (Sandbox Code Playgroud)

main()不知道什么样的对象CreateRandom()会返回,所以C风格的演员Bar* bar = (Bar*)base;阵容绝对不是类型安全的.你怎么能解决这个问题?一种方式是像布尔函数添加AreYouABar() const = 0;到基类,并返回trueBarfalseFoo.但还有另一种方法:使用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;

}
Run Code Online (Sandbox Code Playgroud)

强制转换在运行时执行,并通过查询对象来工作(无需担心现在如何),询问它是否是我们正在寻找的类型.如果是,则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;
}
Run Code Online (Sandbox Code Playgroud)

向基础添加虚拟功能(例如虚拟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;
}
Run Code Online (Sandbox Code Playgroud)

  • 为什么编译器首先会抱怨它?当我们只为基地提供虚拟指示器时,它们不会吗? (7认同)
  • @Coderx7 dynamic_cast 需要运行时类型信息 (RTTI),该信息仅适用于多态类,即具有至少一种虚方法的类。 (5认同)
  • 应该注意的是,如果你做`Base*base = new Base;`,`dynamic_cast <Foo*>(base)`将是`NULL`. (3认同)
  • @munesh因为`base`不是`Foo`.一个`Base`指针可以指向一个`Foo`,但它仍然是一个`Foo`,所以动态转换会起作用.如果你做`Base*base = new Base`,`base`是`Base`,而不是`Foo`,所以你不能动态地将它转换为`Foo`. (3认同)
  • 顺便说一句,如果您[运行(第一个)代码](https://onlinegdb.com/SJeRRzUcRU),您会注意到每次调用 `bar-&gt;BarIt();` 都会打印 `baring It... `,即使对于 `Foo` 类也是如此。尽管现在的答案已经很过时了,但它可能会成为关于_为什么_的一个很好的评论。也就是说,类定义中“BarIt”和“FooIt”的重合重叠 vtable 条目。 (2认同)

Chr*_*ung 20

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

但是,为了帮助您理解RTTI(以及dynamic_cast更多),您应该阅读<typeinfo>标题和typeid操作符.这将返回与您手头的对象相对应的类型信息,您可以从这些类型的信息对象中查询各种(有限的)事物.


Dav*_*eas 10

不仅仅是C中的代码,我认为英语定义就足够了:

给定一个类Base,它有一个派生类Derived,dynamic_cast当且仅当指向的实际对象实际上是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
}
Run Code Online (Sandbox Code Playgroud)

在示例中,调用test将不同的对象绑定到引用Base.在内部,参考downcasted的引用Derived类型安全方式:丧气将只对那些情况下,被引用的对象确实是一个实例成功Derived.

  • 我认为最好澄清一下,如果类是多态的,即至少 Base 类至少有一个虚拟方法,那么上面共享的示例将基于假设工作。 (2认同)

Dav*_*yna 6

首先,为了用 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])();
Run Code Online (Sandbox Code Playgroud)

那么动态转换是这样的:

// A * ptr = new B();
A * ptr = (A*) newB();
// B * aB = dynamic_cast<B>(ptr);
B * aB = ( ptr->vtable == Bvtable ? (B*) aB : (B*) 0 );
Run Code Online (Sandbox Code Playgroud)

  • 最初的问题是“我们可以用 C 语言编写 C++ 的dynamic_cast 的等效项吗”。 (4认同)