C + + 中的 Dynamic _ cast 和 static _ cast

我对 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 语言写一个相当于 dynamic_cast的 C + + 吗? 这样我就可以更好地理解一些东西了?

223687 次浏览

除非您正在实现自己手工编写的 RTTI (并绕过系统一) ,否则不可能在 C + + 用户级代码中直接实现 dynamic_castdynamic_cast与 C + + 实现的 RTTI 系统紧密相连。

但是,为了帮助您更好地理解 RTTI (以及 dynamic_cast) ,您应该阅读 <typeinfo>头文件和 typeid操作符。这将返回与您手边的对象相对应的类型信息,您可以从这些类型信息对象中查询各种(有限的)内容。

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

编辑

下面的实现演示了一种技术。我并不是说编译器使用了类似的东西,但我认为它演示了这些概念:

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

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

C 语言中没有类,所以不可能用 C 语言编写 Dynamic _ cast。C 结构没有方法(因此,它们没有虚方法) ,所以没有什么“动态”。

在类型检查方面,下面的内容与你从 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;
}

除了 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实例的情况下,向下转换才会成功。

下面是关于 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 < > s 的情况下,规则很简单。您可以尝试将任何内容强制转换为其他内容,如果 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<>:

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<>进行这种从基础到派生的铸造,基础、 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 添加一个虚函数(比如虚函数 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;
}

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

Dynamic _ cast 使用 RTTI。它可以减慢您的应用程序,您可以使用访问者设计模式的修改,以实现下拉没有 RTTI http://arturx64.github.io/programming-world/2016/02/06/lazy-visitor.html

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