基类中的虚函数调用

65

假设我们有:


class Base
{   
    virtual void f() {g();};
    virtual void g(){//Do some Base related code;}
};

class Derived : public Base
{   
    virtual void f(){Base::f();} override;
    virtual void g(){/*Do some Derived related code*/} override;
};

int main()
{
    Base *pBase = new Derived;
    pBase->f();
    return 0;  
}

Base::g()Derived::g()会被调用,这取决于BaseDerived类中的实现。

谢谢...


5
请使用带有1/0开关的按钮将您的代码格式化得漂亮些。(我进行了编辑,但发帖者回滚了它,所以我不会再次自行编辑) - Johannes Schaub - litb
3
请注意,您提供了一个存在内存泄漏的示例。您在主函数中忘记了使用 delete。 - jaques-sam
9个回答

64

将会调用派生类的g函数。如果你想调用基类中的函数,请调用

Base::g();

相反,如果你想调用派生类中的函数,但仍然希望调用基类版本,请安排派生类版本在其第一条语句中调用基类版本:

virtual void g() {
    Base::g();
    // some work related to derived
}

在基类中调用虚方法并将控制权转移到派生类的函数,被用于模板方法设计模式。在C++中,它更为人所知的称呼是 非虚接口。 它也广泛地应用于C++标准库(例如C++流缓冲区具有调用真正工作的虚函数的函数pub...,例如pubseekoff会调用受保护的seekoff)。我在这个答案中提供了一个示例:如何验证对象的内部状态?


有趣的是,我发现GCC 4.8.2存在一个问题: Base *pBase = (Base*)(void*)new Derived; 尝试从我的基类中调用我的纯虚函数。 - Dzenly
如果在构造函数中调用,那么将调用基类的g(),因为派生类此时尚未构造。 - lbsweek

19

除非在Base的构造函数中调用g,否则它将是Derived::g。因为在构造Derived对象之前会调用Base构造函数,所以不能逻辑上调用Derived::g,因为它可能会操纵尚未构建的变量,因此将调用Base::g。


1
构造函数的运作方式得到了很好的澄清。<br/>Scott Meyers说链接 - AlanB

7

pBase是一个指向基类的指针。 pBase = new Derived返回一个指向Derived的指针 - Derived is-a Base。

所以pBase = new Derived是有效的。

pBase引用了一个Base,因此它会将Derived视为Base。

pBase->f()将调用Derive::f();

然后我们在代码中看到:

Derive::f() --> Base::f() --> g() - 但是哪个g?

好吧,它调用的是Derive::g(),因为这是pBase“指向”的g。

答案:Derive::g()


2

嗯...我不确定这个应该能编译通过。以下是代码:


Base *pBase = new Derived;

如果您没有以下条件,则无效:

Class Derived : public Base

这是您的意思吗?如果是这样,
pBase->f();

那么调用栈将会如下所示:

Derived::f()
    Base::f()
        Derived::g()

1

由于您将g()定义为虚函数,因此无论您的代码当前访问的类型是什么,都将在类的vtable中查找最终派生的g()并进行调用。

请参阅C++ FAQ on virtual functions


1

实际运行您的代码会显示调用Derived::g()。


1

如果在成员函数中,派生类的g()将被调用。

如果在构造函数或析构函数中,则调用基类的g()。

https://www.geeksforgeeks.org/calling-virtual-methods-in-constructordestructor-in-cpp/

// calling virtual methods in constructor/destructor
#include<iostream> 
using namespace std; 

class dog 
{ 
public: 
    dog()  
    { 
        cout<< "Constructor called" <<endl; 
        bark() ; 
    } 

    ~dog() 
    {  
        bark();  
    } 

    virtual void bark() 
    {  
        cout<< "Virtual method called" <<endl;  
    } 

    void seeCat()  
    {  
        bark();  
    } 
}; 

class Yellowdog : public dog 
{ 
public: 
        Yellowdog()  
        { 
            cout<< "Derived class Constructor called" <<endl;  
        } 
        void bark()  
        { 
            cout<< "Derived class Virtual method called" <<endl;  
        } 
}; 

int main() 
{ 
    Yellowdog d; 
    d.seeCat(); 
} 

输出:

Constructor called
Virtual method called
Derived class Constructor called
Derived class Virtual method called
Virtual method called

0

派生类的方法将被调用。

这是因为在具有虚函数和覆盖这些函数的类中包含了vtable。 (这也称为动态分派。) 这里是实际发生的事情:为Base创建了一个vtable,为Derived创建了一个vtable,因为每个类只有一个vtable。因为pBase调用了一个虚拟且被覆盖的函数,所以会调用指向Derived的vtable的指针。称之为d_ptr,也称为vpointer:

int main()
{
    Base *pBase = new Derived;
    pBase->d_ptr->f();
    return 0;  
}

现在d_ptr调用Derived :: f(),它调用Base :: f(),然后查看虚表以查看要使用哪个g()。因为vpointer仅知道Derived中的g(),所以我们使用它。因此,调用Derived::g()


0

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接