嵌入式系统与单片机|技术阅读
登录|注册

您现在的位置是:嵌入式系统与单片机 > 技术阅读 > C++纯虚函数详解

C++纯虚函数详解


01

C++纯虚函数探索


C++的运行时多态由虚函数或纯虚函数实现,纯虚函数没有函数定义,在声明的时候要在函数名后面加"= 0",拥有纯虚函数的类我们称为抽象类。从我们知道,带有虚函数的类内部会有一个虚函数表指针vfptr,同理带有纯虚函数的类内部也有一个虚函数表指针vfptr,但是虚函数和纯虚函数之间又有一些不一样,先看下面这段代码。


#include <iostream>

class Base{
public:
    virtual void fun1() = 0;

    virtual void fun2() = 0;
};

int main()
{
    Base* b = new Base();
    return 0;
}

编译信息如下:

编译出错了!由编译信息可以看出,类Base不能创建实例。从代码里我们看到类Base成员函数都是纯虚函数,如果有非虚函数在里面时,能否创建一个实例呢?增加一个非纯虚函数fun3,代码如下:class Base{
public:
    virtual void fun1() = 0;

    virtual void fun2() = 0;

    void fun3(){
        std::cout << "Base::fun3 run" << std::endl;
    }

    virtual ~Base() = default;
};

编译运行依然出错!

由此可以得出,抽象类不能创建实例。在C++里,一个类继承抽象类,若子类也有纯虚函数,那么子类包含有从基类继承而来的纯虚函数和自身的纯虚函数,同样也是抽象类。现在有一个非抽象类Derive继承类Base,和虚函数不同的是,非抽象类继承抽象类,子类必须实现基类所有的纯虚函数,代码如下:#include <iostream>

class Base{
public:
    virtual void fun1() = 0;

    virtual void fun2() = 0;

    void fun3(){
        std::cout << "Base::fun3 run" << std::endl;
    }

    virtual ~Base(){
        std::cout << "Base::desconstructor run" << std::endl;
    };
};

class Derive : public Base{
public:
    void fun1(){
        std::cout << "Derive::fun1 run" << std::endl;
    }

    void fun2(){
        std::cout << "Derive::fun2 run" << std::endl;
    }

    ~Derive(){
        std::cout << "Derive::desconstructor run" << std::endl;
    }
};

int main()
{
    Base* b = new Derive();

    b->fun1();
    b->fun2();

    delete b;

    return 0;
}
编译运行输出如下:


Derive::fun1 run
Derive::fun2 run
Derive::desconstructor run
Base::desconstructor run

和虚函数一样,基类指针可以保存派生类实例地址,可以通过基类指针调用派生类实现函数。看到这里眼尖的人就会发现,抽象类的析构函数是虚析构函数,如果析构函数不是虚析构函数呢?运行会出现什么问题?下面这段代码会演示非虚析构函数会发生什么。

class Base{
public:
    virtual void fun1() = 0;

    virtual void fun2() = 0;

    void fun3(){
        std::cout << "Base::fun3 run" << std::endl;
    }

    ~Base(){
        std::cout << "Base::desconstructor run" << std::endl;
    };
};
编译运行输出如下:


Derive::fun1 run
Derive::fun2 run
22:42:50: 程序异常结束。

程序在析构发生了异常!这是因为delete使用的是基类指针而不是派生类的指针,那么我们现在将主函数改成如下:

int main()
{
    Derive* d = new Derive();

    d->fun1();
    d->fun2();

    delete d;

    return 0;
}

编译输出如下:

Derive::fun1 run
Derive::fun2 run
Derive::desconstructor run
Base::desconstructor run
现在输出正常了。之前到底是怎么回事呢?这是因为在调用delete时使用的是基类指针,且基类是抽象类,如果基类析构函数不是虚析构函数,在释放内存时找不到派生类的析构函数,因此抽象类的析构函数必须是虚析构函数才能通过基类指针完全释放基类和派生类内存。通过基类调用派生类函数过程:通过虚函数表指针vfptr找到虚函数表vftable,再从虚函数表vftable找到派生类函数地址,进而调用实际的派生类函数。有关虚函数表和虚函数表指针可以查看

往期推荐