【问题标题】:dynamic_cast and static_cast in C++C++ 中的 dynamic_cast 和 static_cast
【发布时间】:2011-01-16 05:26:05
【问题描述】:

我对 C++ 中的 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 关键字从一个指针或引用转换数据 键入另一个,执行运行时检查以确保强制转换的有效性

我们能否在 C 中写出与 C++ 的 dynamic_cast 等效的内容,以便我更好地理解事物?

【问题讨论】:

  • 如果您想了解 dynamic_cast&lt;&gt; 在幕后的工作原理(或 C++ 的工作原理),一本好书(对于技术性很强的东西也很容易阅读) Lippman 的“C++ 对象模型内部”。 Stroustrup 的“C++ 的设计和演变”和“C++ 编程语言”书籍也是很好的资源,但 Lippman 的书专注于 C++ 如何在“幕后”工作。
  • B* b2 = dynamic_cast&lt;B*&gt; (ap) // 'b' 行中的注释是什么意思? b2 is pointer to b 还是什么?
  • @BogdanSikach 这是什么问题?它只是意味着ap现在是B类的一种

标签: c++ casting dynamic-cast


【解决方案1】:

这里是 static_cast&lt;&gt;dynamic_cast&lt;&gt; 的概要,特别是它们与指针有关。这只是一个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* 类型的指针。但是这个转换是在运行时执行的,而不是编译时。因为这是一个运行时强制转换,所以在与多态类结合使用时尤其有用。事实上,在某些情况下,类必须是多态的,才能使强制转换合法。

强制转换可以沿两个方向之一进行:从基础到派生 (B2D) 或从派生到基础 (D2B)。看看 D2B 转换如何在运行时工作很简单。 ptr 是从 Type 派生的,或者不是。对于 D2B dynamic_casts,规则很简单。您可以尝试将任何内容转换为其他任何内容,如果ptr 实际上是从Type 派生的,您将从dynamic_cast 得到一个Type* 指针。否则,你会得到一个 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; 的函数添加到基类,并从 Bar 返回 true,从 Foo 返回 false。但是还有另一种方式:使用dynamic_cast&lt;&gt;

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&lt;Type*&gt; 返回一个指针;否则返回 NULL。

为了使用dynamic_cast&lt;&gt; 进行这种从基到派生的转换,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;
}

向 base 添加一个虚函数,例如 virtual 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;
}

【讨论】:

  • 为什么编译器首先抱怨它?当我们只为基地提供一个虚拟医生时,不是吗?
  • 需要注意的是,如果你做Base* base = new Base;dynamic_cast&lt;Foo*&gt;(base)将是NULL
  • @Coderx7 dynamic_cast 需要运行时类型信息 (RTTI),该信息仅适用于多态类,即具有至少一个虚拟方法的类。
  • @munesh 因为base 不是FooBase 指针可以指向 Foo,但它仍然是 Foo,因此可以使用动态转换。如果你做Base* base = new BasebaseBase,而不是Foo,所以你不能将它动态转换为Foo
  • 顺便说一句,如果你run the (first) code 你会注意到每次调用bar-&gt;BarIt(); 都会打印baring It... ,即使是Foo 类。尽管现在的答案已经过时了,但它可能会对为什么做出很好的评论。即,类定义中BarItFooIt 的巧合重叠vtable 条目。
【解决方案2】:

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

但是,为了帮助您更多地了解 RTTI(以及 dynamic_cast),您应该阅读 &lt;typeinfo&gt; 标头和 typeid 运算符。这将返回与您手头的对象对应的类型信息,您可以从这些类型信息对象中查询各种(有限的)事物。

【讨论】:

  • 我会把你指向维基百科,但它关于 RTTI 和 dynamic_cast 的文章非常少。 :-P 自己玩,直到你掌握它的窍门。 :-)
【解决方案3】:

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

假设有一个派生类 Derived 的 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 的引用:向下转换只会在被引用对象确实是Derived 实例的情况下成功。

【讨论】:

  • 我认为最好澄清一下,如果类仅是多态的,即至少基类至少有一个虚拟方法,那么上面共享的示例将基于假设工作。
  • 这将失败,因为类不是多态类型。
【解决方案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;
}

【讨论】:

    【解决方案5】:

    首先,为了用 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 );
    

    【讨论】:

    • 最初的问题是“我们能否在 C 中编写等效于 C++ 的 dynamic_cast”。
    【解决方案6】:

    dynamic_cast 使用 RTTI 执行类型检查。如果它失败了,它会抛出一个异常(如果你给了它一个引用),或者如果你给它一个指针,它会抛出 NULL。

    【讨论】:

      【解决方案7】:

      C 中没有类,因此不可能用该语言编写 dynamic_cast。 C 结构没有方法(因此,它们没有虚拟方法),因此其中没有任何“动态”。

      【讨论】:

        【解决方案8】:

        不,不容易。编译器为每个类分配一个唯一标识,该信息被每个对象实例引用,这就是在运行时检查以确定动态转换是否合法的内容。您可以使用此信息和运算符创建一个标准基类,以对该基类进行运行时检查,然后任何派生类都会通知基类它在类层次结构中的位置,并且这些类的任何实例都可以通过以下方式进行运行时强制转换你的操作。

        编辑

        这是一个演示一种技术的实现。我没有声称编译器使用了类似的东西,但我认为它展示了这些概念:

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

        【讨论】:

          【解决方案9】:

          static_cast&lt; Type* &gt;(ptr)

          C++ 中的static_cast 可用于所有类型转换都可以在编译时验证的场景。

          dynamic_cast&lt; Type* &gt;(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;
          }
          

          更多信息click在这里

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

          【讨论】:

            【解决方案10】:

            dynamic_cast 使用 RTTI。它会减慢您的应用程序,您可以使用访问者设计模式的修改来实现无需 RTTI 的向下转换http://arturx64.github.io/programming-world/2016/02/06/lazy-visitor.html

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 2011-06-06
              • 2010-11-18
              • 2014-05-29
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2010-09-24
              相关资源
              最近更新 更多