我不明白为什么以下代码示例有效,并希望得到一些澄清。在我看来,由于derivedMethod
是Derived
的非静态方法,因此它只能从实例化的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 ++编码风格。这只是一个关于为什么我提供的代码示例有效的问题,因为它模仿了我在野外发现的代码。
答案 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类的接口,因此可以假设数据存在于实际上不存在的地方。在您的示例中,即使在实例化时,这两个对象在技术上也是抽象的,因为没有与之关联的数据。如果它们各自具有不同的属性,则方法可能引用错误的属性或完全无关联的内存。