• Index

析构函数和对象的生命周期

Reads: 28

构造函数就是当一个对象创建的时候,自动调用的函数,那么与之对应的就是析构函数。析构函数就是当一个对象将被销毁的时候,自动调用的函数。

析构函数

point_t的析构函数的声明如下:

~point_t(void);

析构函数是一个特殊的成员函数,和构造函数相对应。析构函数的函数名称就是~后面加类名,并且它没有返回值,而且它不能有参数。析构函数就是在对象将要结束之前,会自动地被调用的函数,一般会在析构函数里面做资源的释放工作。

point_t的析构函数的定义格式如下:

point_t::~point_t(void)
{
}

基础示例

#include <iostream> // std::cout std::endl

class point_t
{
public:
    ~point_t(void);
};

int main(void)
{
    // 手动控制point的作用域, 这样就能方便看出运行结果
    {
        point_t point;
        std::cout << "对象point创建完毕" << std::endl;
    }
    std::cout << "程序执行完毕" << std::endl;
    return 0;
}

point_t::~point_t(void)
{
    std::cout << "析构函数执行中..." << std::endl;
}

输出结果:

对象point创建完毕
析构函数执行中...
程序执行完毕

基础讲解

从运行结果可以看出,析构函数是自动调用的。

当输出完对象point创建完毕之后就是},意味着point对象将要被销毁,那么此时就会自动调用析构函数,输出析构函数执行中...。最后输出一行程序执行完毕

析构函数的异常问题

类的析构函数都是默认noexcept

我们知道对象是先正常执行完析构函数后才会释放对象内存,而在析构函数中抛出异常,由于异常不能正常结束,就会导致所有对象的内存不能释放而导致内存泄漏。所以析构函数默认指定为noexcept

对象的生命周期

或者你看到这里会对对象的创建、销毁、构造函数、析构函数的顺序会产生疑问。现在就是理清它顺序的时候。

C++有一个机制叫做RAII。RAII机制就是所有的变量在创建的时候都会调用构造函数,在即将被销毁(即离开其作用域)的时候都会调用析构函数。

基础示例 1

接下来,用简单的代码说明一切:

#include <iostream>

class Test
{
public:
    Test(int value)
        : m_value(value)
    {
        std::cout << "Test的构造函数:" << m_value << std::endl;
    }
    ~Test(void)
    {
        std::cout << "Test的析构函数:" << m_value << std::endl;
    }
private:
    int m_value;
};

int main(void)
{
    {
        Test test(23333);
    }
    return 0;
}

输出结果:

Test的构造函数:23333
Test的析构函数:23333

基础示例 2

上面栗子,我们知道了创建的时候会调用构造函数,离开作用域被释放时会调用析构函数。但是我们不知道究竟是先创建成员变量再调用构造函数还是相反;也不知道是先释放成员变量再调用析构函数还是相反。下面的栗子又可以说明一切。

#include <iostream>

class Test
{
public:
    Test(int value)
        : m_value(value)
    {
        std::cout << "Test的构造函数:" << m_value << std::endl;
    }
    ~Test(void)
    {
        std::cout << "Test的析构函数:" << m_value << std::endl;
    }
private:
    int m_value;
};

class MyTest
{
public:
    MyTest(void)
        : m_test(23333)
    {
        std::cout << "MyTest的构造函数" << std::endl;
    }
    ~MyTest(void)
    {
        std::cout << "MyTest的析构函数" << std::endl;
    }
private:
    Test m_test;
};

int main(void)
{
    {
       MyTest test;
    }
    return 0;
}

输出结果:

Test的构造函数:23333
MyTest的构造函数
MyTest的析构函数
Test的析构函数:23333

基础讲解

上面代码也就说明了:先创建好所有的成员变量,然后再调用构造函数;释放的时候先调用析构函数,然后再释放成员变量。

上面的代码就是先创建成员变量m_test,然后再调用MyTest的构造函数,以供对成员变量进行初始化操作;释放的时候先调用MyTest析构函数,以供手动释放某些成员变量的资源,然后再自动释放成员变量m_test

初始化列表的初始化顺序

接下来再看看,如果有多个成员变量的时候,这些成员变量的初始化顺序。先看栗子:

基础示例

#include <iostream>

class Test
{
public:
    Test(int value)
        : m_value(value)
    {
        std::cout << "Test的构造函数:" << m_value << std::endl;
    }
    ~Test(void)
    {
        std::cout << "Test的析构函数:" << m_value << std::endl;
    }
private:
    int m_value;
};

class MyTest
{
public:
    MyTest(void)
        : m_test2(23333) // 故意将m_test2放在m_test1前面
        , m_test1(66666)
    {
        std::cout << "MyTest的构造函数" << std::endl;
    }
    ~MyTest(void)
    {
        std::cout << "MyTest的析构函数" << std::endl;
    }
private:
    Test m_test1; // 这里m_text1在m_test2前面
    Test m_test2;
};

int main(void)
{
    {
       MyTest test;
    }
    return 0;
}

输出结果:

Test的构造函数:66666
Test的构造函数:23333
MyTest的构造函数
MyTest的析构函数
Test的析构函数:23333
Test的析构函数:66666

基础讲解

上面的代码不止看到成员变量初始化的顺序,还看到它们释放的顺序。那么先讲成员变量创建初始化的顺序。

上面的代码中,类成员变量m_test1m_test2上面,而故意在初始化列表中将m_test2放在m_test1上面。而我们从结果可以看到,它是按成员变量放置的顺序来创建和初始化而不是按初始化列表的顺序。

而释放的话,有目共睹就不说了。(手动微笑脸

总结一下:成员变量的创建和初始化顺序是按照成员变量在类中的位置决定的。

巩固练习

  1. 完成simple_vector的析构函数,注意内存的释放。
  2. 完成simple_vectorclear(),注意内存的释放。
  3. 完成simple_vectorat(),注意越界问题。

Comments

Make a comment

  • Index

WARNING: You are using an old browser that does not support HTML5. Please choose a modern browser (Chrome / Microsoft Edge / Firefox / Sarafi) to get a good experience.