C++面向对象编程之析构详解

目录
  • 1. 概述
  • 2. 详论
    • 2.1. 对象生命周期
    • 2.2. 不一定需要显式析构
    • 2.3. 析构的必要性
  • 3. 总结

1. 概述

类的析构函数执行与构造函数相反的操作,当对象结束其生命周期,程序就会自动执行析构函数:

class ImageEx
{
public:
    ImageEx()
    {
        cout << "Execute the constructor!" << endl;
    }
    ~ImageEx()
    {
        cout << "Execute the destructor!" << endl;
    }
};
int main()
{
    ImageEx imageEx;
    return 0;
}

那么同样的问题来了,为什么要有析构函数呢?

2. 详论

2.1. 对象生命周期

在经典C++中,需要通过new/delete来手动管理动态内存。如果我们在类中申请一个动态数组,并且通过自定义的函数Release()来释放它:

class ImageEx
{
public:
    ImageEx()
    {
        cout << "Execute the constructor!" << endl;
        data = new unsigned char[10];
    }
    ~ImageEx()
    {
        cout << "Execute the destructor!" << endl;
    }
    void Release()
    {
        delete[] data;
        data = nullptr;
    }
private:
    unsigned char * data;
};
int main()
{
    {
        ImageEx imageEx;
        imageEx.Release();
    }
    return 0;
}

那么,当类对象离开作用域,结束生命周期之前,就必须显示调用一次成员函数Release(),否则就会造成内存泄漏:对象在调用析构函数之后,只会销毁数据成员data本身,而不是其指向的内存。

那么,一个合理的实现是,将成员函数Release()放入到析构函数:

class ImageEx
{
public:
    ImageEx()
    {
        cout << "Execute the constructor!" << endl;
        data = new unsigned char[10];
    }
    ~ImageEx()
    {
        Release();
        cout << "Execute the destructor!" << endl;
    }
private:
    unsigned char * data;
    void Release()
    {
        delete[] data;
        data = nullptr;
    }
};
int main()
{
    {
        ImageEx imageEx;
    }
    return 0;
}

这样,当类对象离开作用域,结束生命周期之前,就自动通过析构函数,实现了动态数组的释放。好处是显而易见的:实现了类似于内置数据类型对象的生命周期管理,我们可以像使用内置数据类型对象一样使用类对象。

2.2. 不一定需要显式析构

在一些现代高级编程语言(C#、Java、Javascript)中,已经不用去手动管理动态内存,取而代之的,是其与操作系统的中间件(.net,jvm,浏览器)的GC(垃圾回收)机制。而在现代C++中,提倡通过智能指针(std::shared_ptr、std::unique_ptr、std::weak_ptr)来管理动态内存;对于动态数组,则使用标准容器std::vector则更好。在两者的内部都实现了前文提到的对象生命周期管理,在离开作用域后,通过析构函数自动释放管理的内存,无需再手动进行回收。

那么,一个显而易见的推论就出来了,如果我们在类中使用智能指针或者vector容器来替代new/delete管理动态内存,是不是就可以不用析构函数了?严格来说,是不用显式使用析构函数:

class ImageEx
{
public:
    ImageEx():
        data(10)
    {
        cout << "Execute the constructor!" << endl;
    }
private:
    std::vector<unsigned char> data;
};
int main()
{
    ImageEx imageEx;
    return 0;
}

实际上,并不是这个类不存在析构函数,而是编译器会为它生成一个合成的析构函数,在这个析构函数体中,什么也不用做。因为类中的动态内存,已经交由std::vector容器来管理。当类对象离开作用域调用析构函数之后,会销毁这个std::vector容器数据成员,进而触发其析构函数,释放其管理的内存。

2.3. 析构的必要性

根据上一节内容,不一定需要显式析构。因为现代C++的一些机制能够帮你自动管理动态内存。但是析构函数还是必要的,这是由于C++语言本身的性质决定的。作为C语言大部分内容的超集,需要兼容C语言手动管理内存的特性。更重要的是,现代操作系统几乎全部由C语言编写,与底层的交互不可避免的需要手动使用动态内存管理。

3. 总结

所以我们就能理解了,C++这门语言的设计哲学就是就是这样:既想要C语言的高性能,也想要高级语言高度抽象的特性。如果我们必须兼容C语言底层设计,那我们最好使用析构函数释放动态内存;否则多数情况下,我们应该使用智能指针或者stl容器来管理动态内存,从而避免显示使用析构函数。

本篇文章就到这里了,希望能够给你带来帮助,也希望您能够多多关注我们的更多内容!

(0)

相关推荐

  • 一篇文章带你了解C++面向对象编程--继承

    目录 C++ 面向对象编程 -- 继承 总结 C++ 面向对象编程 -- 继承 "Shape" 基类 class Shape { public: Shape() { // 构造函数 cout << "Shape -> Constructor" << endl; } ~Shape() { // 析构函数 cout << "Shape -> Destructor" << endl; } vo

  • 分享一下8年C++面向对象设计的经验体会

    六年前,我刚热恋"面向对象"(Object-Oriented)时,一口气记住了近十个定义.六年后,我从几十万行程序中滚爬出来准备写点心得体会时,却无法解释什么是"面向对象",就象说不清楚什么是数学那样.软件工程中的时髦术语"面向对象分析"和"面向对象设计",通常是针对"需求分析"和"系统设计"环节的."面向对象"有几大学派,就象如来佛.上帝和真主用各自的方式定义了这个

  • C++面向对象之多态的实现和应用详解

    前言 本文主要给大家介绍的是关于C++面向对象之多态的实现和应用的相关内容,分享出来供大家参考学习,下面话不多说了,来一起看看详细的介绍吧. 多态 大家应该都听过C++三大特性之一多态,那么什么多态呢?多态有什么用?通俗一点来讲-> 多态性可以简单地概括为"一个接口,多种方法",程序在运行时才决定调用的函数,它是面向对象编程领域的核心概念.当多态应用形参类型的时候,可以接受更多的类型.当多态用于返回值类型的时候,可以返回更多类型的数据.多态可以让你的代码拥有更好的扩展性. 多态分

  • C++图文并茂轻松进阶面向对象

    目录 一.进阶面向对象(上) 二.小结(上) 三.进阶面向对象(下) 四.小结(下) 一.进阶面向对象(上) 面向对象的意义在于 将日常生活中习惯的思维方式引入程序设计中 将需求中的概念直观的映射到解决方案中 以模块为中心构建可复用的软件系统 提高软件产品的可维护性和可扩展性 类和对象是面向对象中的两个基本概念 类∶指的是一类事物,是一个抽象的概念 对象∶指的是属于某个类的具体实体 类是一种模型,这种模型可以创建出不同的对象实体 对象实体是类模型的一个具体实例 注:一个类可以有很多对象,而一个对

  • 关于C++面向对象设计的访问性问题详解

    前言 最近在看Scott Meyers大神的<Effective C++>和<More Effective C++>,虽然这两本书都是古董级的教参了(当然针对C++11/C++14作者所更新的<Modern Effective C++>英文已经发售了,不过还没中文翻译版本),但是现在看来仍然收益匪浅,而且随着对这个复杂语言了解的深入和实践项目经验的增加,很多东西和作者产生了一种共鸣,以前种种疑惑突然有种拨云雾而见天日.豁然开朗的感觉,也难怪被列为合格C++程序员之必读书

  • C++面向对象编程之析构详解

    目录 1. 概述 2. 详论 2.1. 对象生命周期 2.2. 不一定需要显式析构 2.3. 析构的必要性 3. 总结 1. 概述 类的析构函数执行与构造函数相反的操作,当对象结束其生命周期,程序就会自动执行析构函数: class ImageEx { public: ImageEx() { cout << "Execute the constructor!" << endl; } ~ImageEx() { cout << "Execute

  • python 编程之twisted详解及简单实例

    python 编程之twisted详解 前言: 我不擅长写socket代码.一是用c写起来比较麻烦,二是自己平时也没有这方面的需求.等到自己真正想了解的时候,才发现自己在这方面确实有需要改进的地方.最近由于项目的原因需要写一些Python代码,才发现在python下面开发socket是一件多么爽的事情. 对于大多数socket来说,用户其实只要关注三个事件就可以了.这分别是创建.删除.和收发数据.python中的twisted库正好可以帮助我们完成这么一个目标,实用起来也不麻烦.下面的代码来自t

  • java并发编程之cas详解

    CAS(Compare and swap)比较和替换是设计并发算法时用到的一种技术.简单来说,比较和替换是使用一个期望值和一个变量的当前值进行比较,如果当前变量的值与我们期望的值相等,就使用一个新值替换当前变量的值.这听起来可能有一点复杂但是实际上你理解之后发现很简单,接下来,让我们跟深入的了解一下这项技术. CAS的使用场景 在程序和算法中一个经常出现的模式就是"check and act"模式.先检查后操作模式发生在代码中首先检查一个变量的值,然后再基于这个值做一些操作.下面是一个

  • Java并发编程之ThreadLocal详解

    目录 一.什么是ThreadLocal? 二.ThreadLocal的使用场景 三.如何使用ThreadLocal 四.数据库连接时的使用 五.ThreadLocal工作原理 六.小结 七.注意点 一.什么是ThreadLocal? ThreadLocal叫做线程本地变量,ThreadLocal中填充的变量属于当前线程,该变量对其他线程而言是隔离的.ThreadLocal为变量在每个线程中都创建了一个副本,则每个线程都可以访问自己内部的副本变量. 二.ThreadLocal的使用场景 1.当对象

  • Swift教程之类的析构详解

    在一个类的实例被释放之前,析构函数会被调用.用关键字deinit来定义析构函数,类似于初始化函数用init来定义.析构函数只适用于class类型. 1.析构过程原理 Swift 会自动释放不再需要的实例以释放资源.如自动引用计数那一章描述,Swift 通过自动引用计数(ARC)处理实例的内存管理.通常当你的实例被释放时不需要手动地去清理.但是,当使用自己的资源时,你可能需要进行一些额外的清理.例如,如果创建了一个自定义的类来打开一个文件,并写入一些数据,你可能需要在类实例被释放之前关闭该文件.

  • C++的虚析构详解及实例代码

    C++的虚析构 最近准备复习一遍所有的知识点,先从基础开始做起,用几分钟写个继承和析构吧. 父类为A,子类为B,代码如下: class A { public: A() { cout << "构造A"<< endl; } ~A() { cout << "析构A" << endl; } } class B:public A { public: B() { cout << "构造B"<&

  • jsp 编程之@WebServlet详解

    编写好Servlet之后,接下来要告诉Web容器有关于这个Servlet的一些信息.在Servlet 3.0中,可以使用标注(Annotation)来告知容器哪些Servlet会提供服务以及额外信息.例如在HelloServlet.java中: @WebServlet("/hello.view") public class HelloServlet extends HttpServlet { 只要在Servlet上设置@WebServlet标注,容器就会自动读取当中的信息.上面的@We

  • Vue 进阶教程之v-model详解

    Vue 官网教程上关于 v-model 的讲解不是十分的详细,写这篇文章的目的就是详细的剖析一下, 并介绍 Vue 2.2  v-model改进的地方,然后穿插的再说点 Vue 的小知识. 在 Vue 中,有许多方法和 Angular 相似,这主要是因为 Angular 是 Vue 早期开发的灵感来源.然而,Augular 中存在许多问题,在 Vue 中已经得到解决. v-model 用在 input 元素上时 v-model虽然很像使用了双向数据绑定的 Angular 的 ng-model,但

  • Javascript基础教程之argument 详解

    argument是javascript中函数的一个特殊参数,例如下文,利用argument访问函数参数,判断函数是否执行 复制代码 代码如下: <script type="text/javascript">     function sayHello () {         if (arguments[0] == "bye")         return;         else        alert( "hello" +

  • Java 并发编程之ThreadLocal详解及实例

    Java 理解 ThreadLocal 摘要: ThreadLocal 又名线程局部变量,是 Java 中一种较为特殊的线程绑定机制,用于保证变量在不同线程间的隔离性,以方便每个线程处理自己的状态.进一步地,本文以ThreadLocal类的源码为切入点,深入分析了ThreadLocal类的作用原理,并给出应用场景和一般使用步骤. 一. 对 ThreadLocal 的理解 1). ThreadLocal 概述 ThreadLocal 又名 线程局部变量,是 Java 中一种较为特殊的 线程绑定机制

随机推荐