我在C ++书中读到,如果指向的对象实际上是派生类型的对象,则可以使用dynamic_cast将指向基础对象的指针向下转换为派生对象指针。格式为dynamic_cast<Derived*>(basePointer)
。此外,我在这个网站上读到,如果它指向的对象无法转换为派生类类型,dynamic_cast应该返回一个空指针。
但是,我最近尝试了一个指向普通对象的指针,该虚拟对象具有向下转换为来自不同类的对象的虚拟函数,以便调用该类中的一个对象。功能。令我惊讶的是,它起作用了:
#include <iostream>
using namespace std;
class Object {
public:
virtual ~Object () {}
};
class Base {
public:
virtual void doSomething () {
cout << "Done something!" << endl;
}
};
class Derived: public Base {
public:
virtual void doSomething () {
cout << "You done goofed!" << endl;
}
void printFoo () {
cout << "Foo" << endl;
}
private:
int x;
};
int main () {
Object o;
Object* p = &o;
if(dynamic_cast<Derived*>(p))
cout << "Yep, baby is derived!" << endl;
else
cout << "Isn't derived." << endl;
dynamic_cast<Derived*>(p)->printFoo();
return 0;
}
这里,Base是一个基类类型,设计用于多态(即,它包含一个虚函数),Derived是一个派生自Base的类类型。 Object只是一个普通类,与任何一个类都无关,但它的析构函数是虚拟的,因此可以多态地使用它。我马上解释Derived :: x的目的。在main函数中,创建一个Object,并将指向Object的指针分配给它的地址。它检查Derived是否是该指针的后代,并打印它是否是或者它不是。然后它将该指针强制转换为Derived类型的指针,并调用Derived的printFoo函数。
这不应该起作用,但确实如此。当我运行它时,它显示&#34;不是派生的,&#34;所以它清楚地知道Obect不是从Base派生出来的,而是印刷了#34; Foo&#34;在屏幕上退出没有错误。但是,如果我将行x = 1;
添加到printFoo函数中,它将以分段错误退出,因为它试图分配一个在该对象中不存在的变量。此外,这似乎只适用于非虚函数。例如,如果我尝试调用虚函数doSomething,我会在#34之前得到分段错误!你做了傻瓜!&#34;永远打印出来。
这里发生了什么?不应该dynamic_cast<Derived*>(p)
返回一个空指针,这样试图从中调用printFoo会自动导致错误吗?为什么它不应该工作?
答案 0 :(得分:2)
这是因为
dynamic_cast<Derived*>(p)->printFoo();
按预期返回null
指针,但null
指针的类型为Derived 。当它调用printFoo
时,它可以工作,因为printFoo
不使用任何成员变量,在这种情况下,它会引用this
来使用该变量,并且会因解除引用{{1}而崩溃指针。由于没有使用成员变量,因此没有对null
的引用,没有问题(这就是为什么当你在方法中使用this
时它会给出段错误。)
与以下简单代码相同:
x
答案 1 :(得分:1)
您在不使用任何实际对象状态的情况下访问null
指针,虽然没有任何意义,但这是安全的。
如果您将Derived :: printFoo()更改为以下内容,您实际上将处于&#34;未定义的行为区域&#34;,并且很可能会出现段错误:
void printFoo () {
cout << "Foo: " << x << endl;
}
对于它的价值,使用引用而不是指针将使得抛出异常,如果你宁愿向外传播失败而不是测试并在本地处理它:
dynamic_cast<Derived &>(*p).printFoo(); // kaboom! throws std::bad_cast
答案 2 :(得分:1)
您已经确定dynamic_cast
正在返回一个NULL指针,所以真正的问题是为什么对NULL对象指针的函数调用在某些情况下似乎有效但在其他情况下却不起作用。
在 ALL 这些情况下,您将获得未定义的行为。请记住,undefined并不总是意味着崩溃 - 有时你会得到完全合理的结果。你不能依赖它。
以下是基于可能的内容的解释,但无法保证它明天的工作方式相同,更不用说更改优化设置或获得新版本的编译器。
printFoo
不是虚拟的,因此无需使用vtable来访问它。 doSomething
是虚拟的,因此 需要vtable。 NULL指针没有vtable,因此调用doSomething
会立即爆炸。
printFoo
在您向其添加x = 1
行之前,不会使用该对象的任何成员。只要编译器没有生成任何访问this
指针的代码,它就可以正常工作。