正在调用基础 class 函数,而不是派生 class 中定义的重写函数

Base class function being called instead of overridden function defined in Derived class

这是我的代码

class BaseClass
{
public:
    BaseClass() {}
    void init(const int object) { cout<<"BaseClass::init"<<endl; }
    void run(const int object) { cout<<"BaseClass::run calls =>";    init(object); }
};

class Derived : public BaseClass {
public:
    Derived() {}
    void init(const int object) { cout<<"Derived::init"<<endl; }
};

int main() {
    BaseClass b;
    b.init('c');
    b.run('c');


    Derived d;
    d.init(5); // Calls Derived::init
    d.run(5);  // Calls Base::init. **I expected it to call Derived::init**
}

这是生成的输出

BaseClass::init
BaseClass::run calls =>BaseClass::init
Derived::init
BaseClass::run calls =>BaseClass::init

通过调用 d.run(5),为什么调用“BaseClass::init”而不是“BaseClass::init”? 我虽然只有在通过指针调用时才需要虚函数。

保持这种行为背后的理由是什么?

I though we need virtual functions only when calling through a pointer.

没有。那是不对的。当您想要根据对象的动态类型调用它时,您需要将函数设为虚函数。一旦你有了虚函数,你就可以使用指针或引用来利用虚拟分派。

当你想重写一个方法时,你需要使它成为虚拟的。由于 BaseClass::run 未被 Derived::run 覆盖,因此不存在从 BaseClass::run 调用 BaseClass::init 的虚拟分派和调用 init。如果要为 BaseClass::init 启用虚拟调度,则需要将其声明为虚拟。

此外,考虑到您的代码等同于:

void run(const int object) { 
    cout<<"BaseClass::run calls =>";    
    this->init(object); 
}

thisBaseClass*,因此调用 BaseClass::init。您正在通过指针调用 init ,但这并不重要,因为 init 不是虚拟的。如果您想根据对象的动态类型调用 init,那正是 virtual 的用武之地。您在评论中提到的“开销”并不是真正的开销,而只是获得您想要的行为所需的最低限度。如果您可以更改设计并且不需要运行时多态性,您可以看看 CRTP,它是静态多态性的一种形式。

Why "BaseClass::init" is being called instead of "BaseClass::init" ?

因为init是一个non-virtual成员函数。为了达到预期的效果,你需要让init成为一个虚拟成员函数,如下所示:

class BaseClass
{
public:
    BaseClass() {}
    //NOTE THE VIRTUAL KEYWORD HERE
    virtual void init(const int object) { cout<<"BaseClass::init"<<endl; }
    //other member function here as before
};

Demo


I though we need virtual functions only when calling through a pointer.

请注意,语句 init(object); 等同于 写作

this->init(object); //here `this` is a pointer 

当你写道:

d.run(5);

在上面的语句中,first对象d的地址隐式作为第一个参数传递给隐式 this成员函数的参数run。此隐式 this 参数的类型是 BaseClass*,这是由于 派生到基础转换 。现在,调用 init(object); 等同于 this->init(object);。但是由于 initnon-virtual 成员函数,调用在 编译时 解析,这意味着基数 class init 将被调用。

基本上,当使用派生 class 对象调用成员函数时,编译器首先查看该成员是否存在于派生 class 中。如果没有,它开始沿着 继承链 检查 成员是否已在任何父 class 中定义。它使用它找到的第一个。这意味着对于调用 d.run(5),对名为 run 的成员函数的搜索从派生的 class 开始。但是由于在派生 class 中没有名为 run 的函数,编译器查看直接基 class BaseClass 并找到名为 run 的成员函数。所以它停止搜索并使用这个 found run 成员函数。正如我所说,在您的示例中,init 是 non-virtual,因此调用在编译时解析为基础 class run.

另一方面,如果我们让init成为一个成员函数,那么这个调用将在[=82处解析=] 表示派生的 class init 将被调用。