• C++ 构造函数 & 析构函数


    C++ 构造函数 & 析构函数

    美团二面被问了一个问题,没有答上来,今天整理一下相关知识。
    问:为什么析构函数要声明成虚函数?


    涉及到虚函数,表明这个问题实际上和多态有关系,具体来讲是用基类指针指向的子类对象如何虚构的问题。

    举个例子,以下函数的输出是什么?

    典型代码

    #include <bits/stdc++.h>
    using namespace std;
    
    class A {
    public:
        A() { cout << "A::A()" << endl; }
        virtual ~A() {cout << "A::~A()" << endl; }
    };
    
    class B {
    public:
        B() { cout << "B::B()" << endl; }
        virtual ~B() {cout << "B::~B()" << endl; }
    };
    
    class C : public A {
    public:
        C() { cout << "C::C()" << endl; }
        virtual ~C() {cout << "C::~C()" << endl; }
    private:
        B b;
    };
    
    int main() {
        // C c;
        A* p = new C;
        delete p;
    }
    

    问题解答

    问题揭秘,输出为:

    A::A()
    B::B()
    C::C()
    C::~C()
    B::~B()
    A::~A()
    

    这里首先回答原来的问题,为什么需要虚函数,因为 A *p = new C; 这种方式声明的对象,如果不用虚函数的话,用的是 class A 的析构函数来析构,会造成资源泄漏。可以尝试把上面代码中的 virtual 全部删除,输出为:

    A::A()
    B::B()
    C::C()
    A::~A()
    

    使用虚函数的话,析构时调用的是从对象的虚函数表中查找到的析构函数,也就是 class C 的析构函数,这才是正确析构对象的开始。

    但是上面的析构是三个析构函数都被调用了,这个顺序是怎么来的,底层怎么工作怎么实现的呢?

    关于构造函数和析构函数的实现

    首先是构造函数,构造函数的工作过程如下:

    1. 按声明顺序,调用所有基类的构造函数;
    2. 构造虚函数表,指向正确代码段位置;
    3. 按声明顺序,调用每个成员变量的构造函数;
    4. 依次调用构造函数体中的语句。

    析构函数工作刚好反过来:

    1. 依次调用析构函数体中的语句;
    2. 按声明逆序,调用每个成员变量的析构函数;
    3. 按声明逆序,调用每个基类的析构函数。

    那么问题来了,对于一个继承链很长的子对象,编译器怎么告诉CPU去哪儿找下一个析构函数位置的?
    在虚析构函数的末尾加入几个 call 指令就可以了!子类对象逆序去 call 成员变量和基类的析构函数,基类各自也 call 自身的成员变量和基类的析构函数,这样也就形成一个调用链了。
    同理,构造函数其实也是编译器自动生成了这一系列 call 指令来工作的。

    注意,为了保证析构函数正常调用,所有作为基类的类,其析构函数都必须声明为虚函数;只有必然作为子类的类(比如用了C++11的 final 声明的类),析构函数才不用写虚函数。

    参考

    1. C++对象模型(五)构造与析构
  • 相关阅读:
    山东财经大学新生赛暨天梯赛选拔赛 A 骆驼拼写法
    Code 墓地 问题 A: 看电视(区间贪心)
    第九届蓝桥杯 乘积尾零(Java大数)
    《真正的力量来自内心深处》
    蓝桥杯训练 历届试题 买不到的数目 (猜公式)
    前缀和与差分 算法详解
    蓝桥杯训练 历届试题 回文数字 (暴力求解,毫无任何技术含量)
    蓝桥杯训练 历届试题 最大子阵 (只用了前缀和,没用dp写)
    实习开始
    MVC缺点总结
  • 原文地址:https://www.cnblogs.com/zhcpku/p/15270161.html
Copyright © 2020-2023  润新知