当使用指针来使用具有多态性的函数时,指针必须是基类的吗?

When using a pointer to use functions with polymorphism, does the pointer have to be of the base class?

本文关键字:指针 基类 多态性 函数      更新时间:2023-10-16

当我编译并运行这段代码时:

#include <iostream>
using namespace std;

class Base
{
    public:
    virtual void doSomething()
    {
        cout << endl << "hi this is base";
    }
};
class DerivedA : public Base
{
    public:
    void doSomething()
    {
        cout << endl << "hi this is derivedA";
    }
};
class DerivedB : public Base
{
    public:
    void doSomething(){
        cout << endl << "hi this is derivedB";
    }
};
int main () {
    DerivedB bb;
    Base* cc;
    cc = (DerivedA*) &bb;
    cc->doSomething();
}

输出为hi this is derivedB"

现在,这只是一个情况下,我做了一些严格非法的,我的编译器设法编译它?因为我可以想象,DerivedA指针在调用doSomething时不知道它指向DerivedB对象,因为那里没有基类/派生类关系。

有没有比http://www.cplusplus.com/doc/tutorial/的教程更深入地解释c++继承和多态性背后的复杂性的资源?

cc = (DerivedA*) &bb;

叮叮叮!这里有未定义的行为!

这是不是我做了一些违法的事情

是的,完全是非法的。或者,不完全是非法的,但是未定义的行为,这几乎是非法的。

回答问题标题:不,你不需要基类指针。然而,你不能只是在不相关的类型之间强制转换,并希望它能工作。

DeriveA是Base而DeriveB是Base这两种类型都是从基类派生出来的你可以使用基指针来调用DeriveA或DeriveB(多态性)但是你不能从一个子类型转到另一个

正如Xeo指出的那样,您的代码是可编译的,但具有未定义的行为。您应该使用c++的显式强制转换操作符——static_castdynamic_castreinterpret_castconst_cast——来防止此类错误。

查看有关dynamic_cast的部分:http://www.cplusplus.com/doc/tutorial/typecasting/

cc = (DerivedA*) &bb;

因为DerivedADerivedB没有直接的继承路径,所以不能使用static_castDerivedB*转换到DerivedA*。这意味着C风格强制转换相当于reinterpret_cast

虽然可以保证如果将此reinterpret_cast的结果强制转换回其原始类型,它将产生相同的指针值,但对于中间指针的值没有任何保证,结果是未指定的。这意味着(DerivedA*)&bb可能根本不指向一个有效的对象,并且将该指针转换为Base*(通过将其赋值给cc)的结果可能不指向一个有效的Base类。

由于不能保证cc的值,通过它调用成员函数可能会导致未定义行为

将指向DerivedB对象的指针转换为DerivedA对象,然后像您这样调用DerivedA的一个函数是自找麻烦(本质上是未定义的行为),因为DerivedB不是从类型DerivedA派生的。

就编译器而言,这并不是严格意义上的"非法",但你不应该这样做,如果你想从你的代码中获得可靠的行为,这不是你想要做的事情。

如果你想在运行时做额外的检查,你应该检查dynamic_cast,而不是使用c风格的强制转换。

为什么它可能工作与虚值表的布局方式有关。在g++中,如果使用命令-fdump-class-hierarchy进行编译,将得到虚变量表的转储,如下所示:

Vtable for DerivedA
DerivedA::_ZTV8DerivedA: 3u entries
0     (int (*)(...))0
8     (int (*)(...))(& _ZTI8DerivedA)
16    (int (*)(...))DerivedA::doSomething
...
Vtable for DerivedB
DerivedB::_ZTV8DerivedB: 3u entries
0     (int (*)(...))0
8     (int (*)(...))(& _ZTI8DerivedB)
16    (int (*)(...))DerivedB::doSomething

可以看到,对于类型DerivedADerivedB的两个实例,函数doSomething处于相同的偏移量。在这种情况下,指针指向DerivedB::doSomething,这就是为什么输出是"hi this is derivedB"