为什么可以从指向实例化的基类对象的转换指针调用非静态派生类方法?

时间:2014-02-08 23:42:37

标签: c++ pointers casting

我不明白为什么以下代码示例有效,并希望得到一些澄清。在我看来,由于derivedMethodDerived的非静态方法,因此它只能从实例化的Derived对象(或指向一个对象的指针)中调用。但是,通过将指向实例化的Base对象的指针转换为指针指向Derived,可以调用derivedMethod。为什么呢?

代码:

// compiled with gcc
#include <iostream>
using namespace std;

struct Base { };
struct Derived : Base 
{ 
    void derivedMethod() { cout << "foo" << endl; } 
};

int main()
{
    Base *basePtr = new Base();
    ((Derived *)basePtr)->derivedMethod();
    delete basePtr;

    return 0;
}

输出:

foo

修改

在发布此问题之前,我修改了Derived以包含整数成员,然后我将其输出到derivedMethod。它仍然编译并运行没有任何错误。

修改

我意识到这不是很好的C ++编码风格。这只是一个关于为什么我提供的代码示例有效的问题,因为它模仿了我在野外发现的代码。

3 个答案:

答案 0 :(得分:2)

当你使用强制转换时,你实际上是在说“将它设为Derived *”。因为然后编译器必须按照你的要求进行操作[并且肯定会有更复杂的代码,你可能确实想要这样做,因为你知道指针确实是指向Derived的指针,就在目前你只有一个Base *指针]。

然而,“正确”的方法是使用dynamic_cast<Derived *>(basePtr),如果转换不起作用,它将返回NULL。所以像这样:

Derived *dPtr = dynamic_cast<Derived *>(basePtr);
if (dPtr != NULL)
{
   dPtr->derivedMethod();
}

现在,这是安全的,因为如果basePtr未指向有效的Derived类,则结果将为NULL,而不会进入调用{{}的代码1}}。

另请注意,在您的代码中调用derivedMethod时,根本无法保证实际发生的情况。它可能会崩溃,或者它可能“起作用”。 (在这个简单的例子中,编译器甚至可以检测到这种情况并给出错误 - 但它没有必要,这是因为在更复杂的情况下,编译器无论如何都无法检测到它)。

此外,在derivedMethod中使用成员变量可能会也可能不会导致可检测到的问题。这完全取决于Derived返回的Base对象之后的“正好” - 它可能是“未使用的空间”(在这种情况下,所有内容都“按预期工作”[就像您实际已分配一样) new Base()对象的额外空间 - 但当然值没有被初始化,所以不要使用例如Derived,这需要构造才能安全使用],或者它可能是某种东西很重要,所以如果你写到那个位置,事情就会出现严重错误(例如std::string崩溃,因为你的代码覆盖了delete basePtr需要的东西。但我们正在谈论未定义的行为,以及编译器/运行时系统在这里几乎可以做任何事情,而且“没有”在技术上是错误的,无论多么奇怪。如果代码决定打印带有1000个小数的pi,播放音乐或崩溃。而C和C ++都有很多情况规范说“在这种情况下发生的事情是未定义的。”这很大程度上是因为检测情况并做某事可能很难/昂贵[1]有意义的”。

请注意,这可能“不起作用”:

delete

现在,它可能会显示#include <iostream> using namespace std; struct Base { }; struct Derived : Base { int y; void derivedMethod() { cout << "foo" << endl; y = 77; } }; int main() { Base b; int x; Base *basePtr = &b; x = 42; ((Derived *)basePtr)->derivedMethod(); cout << "x=" << x << endl; return 0; } 。它也可能没有。取决于编译器的确切功能。

[1]比如说,在某些处理器中,编译器必须添加50条额外的指令来检查错误。在另一个处理器上它是一个额外的指令,所以不错。但是生产第一个处理器的公司需要50条额外的指令来检查某些东西,绝对不会要检查这个错误。

答案 1 :(得分:0)

因为在这一刻你有一个Derived*指针。编译器应该怎么知道你没有?如果要在运行时检查是否确实如此,则必须使用dynamic_cast。如果您将Base*重新解释为Derived*,但就像在摘录中一样,您获得未定义的行为,如果derivedMethod访问Derived的任何成员,该程序可能会崩溃{1}}。

答案 2 :(得分:0)

类方法独立于实例化对象存储,与对象的交互实际上只是与对象实现的接口交互。因此,调用类方法不会与类的实例化交互,直到它需要访问类(属性)中的某些存储数据。

通过将Base类指针强制转换为Derived类指针,它现在假定引用的对象实现了Derived类接口,并且非常高兴调用引用Base类对象的Derived类方法。

然而,正如其他人所说,这会导致不确定的行为。由于Base类没有实现Derived类的接口,因此可以假设数据存在于实际上不存在的地方。在您的示例中,即使在实例化时,这两个对象在技术上也是抽象的,因为没有与之关联的数据。如果它们各自具有不同的属性,则方法可能引用错误的属性或完全无关联的内存。