讲师中心 微信公众号
AI工具推荐 视频效率加速

如何在C++中实现一个析构函数_C++析构函数的作用与实现

酷涛姑娘_6260

酷涛姑娘_6260

发布时间:2025-09-21 17:46:01

|

374人浏览过

|

来源于php中文网

原创

析构函数在对象销毁时自动释放资源,防止内存泄露。文章以MyDynamicArray类为例,展示如何通过~MyDynamicArray()释放new分配的内存,并强调RAII原则;接着指出使用智能指针可避免手动管理内存;随后说明基类析构函数必须为virtual,否则通过基类指针删除派生类对象将导致派生类析构函数不被调用,引发资源泄露;最后强调析构函数不应抛出异常,需在内部处理潜在异常以保证异常安全。

如何在c++中实现一个析构函数_c++析构函数的作用与实现

在C++中,析构函数是一个非常特殊且关键的成员函数,它的核心作用是在对象生命周期结束时,执行必要的清理工作。简单来说,当一个对象即将被销毁时,无论是栈上的局部变量超出作用域,还是堆上通过

delete
释放的对象,析构函数都会被自动调用。它主要用来释放对象在构造期间或生命周期内动态分配的资源,比如内存、文件句柄、网络连接等,确保程序不会发生资源泄露。实现析构函数,你只需要在类名前加上一个波浪号
~
,然后定义其函数体即可。

解决方案

实现C++析构函数其实非常直观,它没有参数,也没有返回值类型,甚至不能被重载。一个类只能有一个析构函数。当你需要为你的类处理一些“善后”事宜时,比如你类中的某个成员变量是指向堆内存的指针,或者你打开了一个文件句柄,析构函数就是你释放这些资源的最佳场所。

下面是一个简单的例子,展示了如何在C++中为一个管理动态内存的类实现析构函数:

#include <iostream>
#include <string>

class MyDynamicArray {
public:
    int* data;
    size_t size;

    // 构造函数
    MyDynamicArray(size_t s) : size(s) {
        data = new int[size]; // 动态分配内存
        std::cout << "MyDynamicArray对象创建,分配了 " << size * sizeof(int) << " 字节内存。" << std::endl;
    }

    // 析构函数
    ~MyDynamicArray() {
        delete[] data; // 释放动态分配的内存
        std::cout << "MyDynamicArray对象销毁,释放了内存。" << std::endl;
    }

    // 拷贝构造函数 (为了完整性,虽然不是析构函数主题,但涉及资源管理)
    MyDynamicArray(const MyDynamicArray& other) : size(other.size) {
        data = new int[size];
        for (size_t i = 0; i < size; ++i) {
            data[i] = other.data[i];
        }
        std::cout << "MyDynamicArray对象被拷贝构造。" << std::endl;
    }

    // 拷贝赋值运算符 (为了完整性)
    MyDynamicArray& operator=(const MyDynamicArray& other) {
        if (this != &other) { // 避免自我赋值
            delete[] data; // 释放当前对象的资源
            size = other.size;
            data = new int[size];
            for (size_t i = 0; i < size; ++i) {
                data[i] = other.data[i];
            }
        }
        std::cout << "MyDynamicArray对象被拷贝赋值。" << std::endl;
        return *this;
    }

    void fill(int value) {
        for (size_t i = 0; i < size; ++i) {
            data[i] = value;
        }
    }

    void print() const {
        std::cout << "内容: [";
        for (size_t i = 0; i < size; ++i) {
            std::cout << data[i] << (i == size - 1 ? "" : ", ");
        }
        std::cout << "]" << std::endl;
    }
};

int main() {
    { // 局部作用域
        MyDynamicArray arr1(5);
        arr1.fill(10);
        arr1.print();
    } // arr1 在这里超出作用域,析构函数被调用

    std::cout << "\n--- 另一个对象 ---\n" << std::endl;

    MyDynamicArray* arr2 = new MyDynamicArray(3);
    arr2->fill(20);
    arr2->print();
    delete arr2; // 手动释放堆上的对象,析构函数被调用

    // 尝试展示拷贝构造和赋值,虽然不是析构函数直接主题,但它们与资源管理紧密相关
    std::cout << "\n--- 拷贝操作 ---\n" << std::endl;
    MyDynamicArray arr3(2);
    arr3.fill(5);
    MyDynamicArray arr4 = arr3; // 拷贝构造
    arr4.print();

    MyDynamicArray arr5(1);
    arr5 = arr3; // 拷贝赋值
    arr5.print();

    return 0;
}

在这个例子中,

MyDynamicArray
类在构造函数中通过
new
分配了一块整数数组内存。如果没有析构函数,当
MyDynamicArray
对象被销毁时,这块内存将不会被释放,从而导致内存泄露。析构函数
~MyDynamicArray()
的存在,确保了
delete[] data;
这行代码总能在对象生命周期结束时执行,妥善地回收了资源。这其实就是C++中非常重要的RAII(Resource Acquisition Is Initialization)原则的一个基本体现。

立即学习“C++免费学习笔记(深入)”;

C++析构函数与内存管理:何时需要手动释放资源?

谈到析构函数和内存管理,这几乎是C++编程中最核心也最容易出错的地方。从我的经验来看,你真正需要手动编写析构函数来释放资源,通常是当你直接使用了原始指针(raw pointers)来管理动态分配的内存,或者管理其他系统资源(如文件句柄、数据库连接、互斥锁等)时。

现代C++中,我们强烈推荐使用智能指针(

std::unique_ptr
、
std::shared_ptr
)来管理动态内存。当你使用智能指针时,它们会自动在适当的时候调用
delete
,你就无需再为它们编写析构函数了。这大大降低了内存泄露和悬空指针的风险。例如:

#include <memory> // for std::unique_ptr

class SafeArray {
public:
    std::unique_ptr<int[]> data; // 使用智能指针
    size_t size;

    SafeArray(size_t s) : size(s), data(std::make_unique<int[]>(s)) {
        std::cout << "SafeArray对象创建,内存由unique_ptr管理。" << std::endl;
    }

    // 注意:这里不需要显式析构函数来释放data,unique_ptr会自动处理
    // ~SafeArray() { /* unique_ptr 会自动释放内存 */ }

    // ... 其他成员函数 ...
};

int main() {
    SafeArray arr(10); // arr超出作用域时,data指向的内存会被unique_ptr自动释放
    return 0;
}

尽管智能指针是主流,但总有些场景,比如与C库交互、实现底层数据结构、或者在特定性能敏感的场景下,你可能仍然会直接使用

new
和
delete
。在这种情况下,显式地编写析构函数就变得不可或缺。它确保了资源的生命周期与对象的生命周期同步,对象生则资源在,对象死则资源消。这是一个非常强大的概念,但也要求我们开发者有足够的细心和责任感。

C++虚析构函数的重要性:多态场景下的资源泄露风险解析

虚析构函数(

virtual ~ClassName()
)是C++多态性中一个非常重要的概念,尤其是在涉及继承和基类指针操作时。我曾经就因为对它理解不深,遇到过一些难以察觉的内存泄露问题。

想象一下这个场景:你有一个基类

Base
和一个派生类
Derived
,
Derived
类在构造函数中动态分配了一些内存。

#include <iostream>

class Base {
public:
    Base() { std::cout << "Base Constructor" << std::endl; }
    ~Base() { std::cout << "Base Destructor" << std::endl; } // 非虚析构函数
};

class Derived : public Base {
public:
    int* data;
    Derived() : data(new int[10]) { std::cout << "Derived Constructor, allocated data." << std::endl; }
    ~Derived() {
        delete[] data; // 释放派生类分配的内存
        std::cout << "Derived Destructor, freed data." << std::endl;
    }
};

int main() {
    Base* ptr = new Derived(); // 用基类指针指向派生类对象
    delete ptr; // 通过基类指针删除派生类对象
    return 0;
}

运行这段代码,你会发现输出是:

C++14
C++14

C++14 对 C++11 的修正与增强版本,适合旧系统维护和较老工具链兼容。

下载
Base Constructor
Derived Constructor, allocated data.
Base Destructor

这里的问题在于,当

delete ptr;
执行时,因为
Base
类的析构函数不是虚函数,C++编译器会认为
ptr
指向的是一个
Base
类型的对象,因此只会调用
Base
的析构函数,而不会调用
Derived
的析构函数。结果就是
Derived
类中动态分配的
data
内存没有被释放,造成了内存泄露。

为了解决这个问题,我们需要将基类的析构函数声明为

virtual
:

#include <iostream>

class Base {
public:
    Base() { std::cout << "Base Constructor" << std::endl; }
    virtual ~Base() { std::cout << "Base Destructor" << std::endl; } // 虚析构函数
};

class Derived : public Base {
public:
    int* data;
    Derived() : data(new int[10]) { std::cout << "Derived Constructor, allocated data." << std::endl; }
    ~Derived() {
        delete[] data;
        std::cout << "Derived Destructor, freed data." << std::endl;
    }
};

int main() {
    Base* ptr = new Derived();
    delete ptr; // 现在会正确调用Derived的析构函数
    return 0;
}

这次的输出会是:

Base Constructor
Derived Constructor, allocated data.
Derived Destructor, freed data.
Base Destructor

这正是我们期望的行为。通过将基类析构函数声明为

virtual
,
delete ptr;
会触发多态机制,正确地调用
Derived
类的析构函数,然后再调用
Base
类的析构函数,确保所有资源都被妥善清理。所以,一个经验法则是:如果你的类打算被继承,并且可能通过基类指针删除派生类对象,那么基类的析构函数就应该声明为虚函数。

C++析构函数与异常安全:如何确保资源在异常抛出时也能被清理?

异常安全是C++中一个更高级但同样重要的话题,它关系到你的程序在面对错误和异常时,能否保持资源的一致性和不泄露。析构函数在实现异常安全方面扮演着不可替代的角色。

C++的一个基本原则是,析构函数不应该抛出异常。如果一个析构函数在执行清理工作时抛出了异常,并且这个析构函数是在栈展开(stack unwinding)过程中被调用的(比如另一个函数已经抛出了异常,正在寻找捕获点),那么程序就会面临同时有两个未处理异常的情况,这通常会导致程序立即终止(

std::terminate
)。这显然不是我们希望看到的。

所以,析构函数的核心职责是“默默地”清理资源,不应该引入新的失败点。如果析构函数中调用的某个函数确实可能抛出异常,我们应该在析构函数内部捕获并处理它,或者至少将其抑制,确保析构函数本身不会将异常传播出去。

例如,如果你在析构函数中关闭一个文件句柄,而这个关闭操作可能会失败并抛出异常(尽管在实际的文件I/O库中这种情况不常见,但作为例子):

#include <iostream>
#include <fstream> // for std::ofstream

class MyFileHandler {
public:
    std::ofstream file;
    std::string filename;

    MyFileHandler(const std::string& name) : filename(name) {
        file.open(filename);
        if (!file.is_open()) {
            throw std::runtime_error("无法打开文件:" + filename);
        }
        std::cout << "文件 " << filename << " 已打开。" << std::endl;
    }

    ~MyFileHandler() {
        if (file.is_open()) {
            try {
                file.close(); // 假设close()可能抛出异常
                std::cout << "文件 " << filename << " 已关闭。" << std::endl;
            } catch (const std::exception& e) {
                // 在析构函数中捕获并处理异常,避免传播
                std::cerr << "警告:关闭文件 " << filename << " 时发生异常:" << e.what() << std::endl;
                // 此时通常只能记录日志,无法回滚
            }
        }
    }
};

int main() {
    try {
        MyFileHandler handler("test.txt");
        // ... 对文件进行操作 ...
        // 假设这里发生了另一个异常
        // throw std::runtime_error("主逻辑发生错误!");
    } catch (const std::exception& e) {
        std::cerr << "捕获到异常:" << e.what() << std::endl;
    }
    return 0;
}

在上面的

~MyFileHandler()
中,我们用
try-catch
块包围了
file.close()
,就是为了防止
close()
可能抛出的异常影响到析构函数的异常安全保证。更现代的C++(C++11及以后)引入了
noexcept
关键字,它可以用来明确声明一个函数不会抛出异常。析构函数默认是
noexcept
的,除非它的某个基类或成员的析构函数不是
noexcept
。这进一步强化了析构函数作为可靠清理机制的地位。

归根结底,析构函数就是你给对象生命周期画上一个句号的地方,它应该是一个安静、高效、无副作用的清理者,确保所有借来的资源都能物归原主,不留后患。

热门AI工具

更多
PixTV
PixTV Hot

PixTV是一款面向AIGC内容创作的AI视频生成工具。

WorkBuddy

一款AI办公效率工具,主要用于腾讯云推出的AI原生桌面智能体工作台,适合需要提升相关任务效率的用户。

讯飞智作

讯飞智作是一款AI视频创作工具,AI文本配音工具,数字人课程、营销视频制作。

豆包大模型

豆包大模型是一款由字节跳动推出的企业级大语言模型服务平台。

DeepSeek

DeepSeek是一款面向对话、写作、编程和推理场景的AI大模型工具。

立刻MV
立刻MV Hot

立刻MV是一款AI文本写作工具,AI 音乐视频(MV)创作工具。

SkildArt
SkildArt Hot

SkildArt是一款AI文本写作工具,一站式 AI 视觉创作平台。

Atoms
Atoms Hot

Atoms是一款AI智能体工具,第一支自动构建真实业务的 AI 团队。

Seko
Seko Hot

一款AI视频创作工具,主要用于商汤科技推出的创编一体的AI短视频创作Agent,适合需要提升相关任务效率的用户。

相关专题

更多
resource是什么文件
resource是什么文件

Resource文件是一种特殊类型的文件,它通常用于存储应用程序或操作系统中的各种资源信息。它们在应用程序开发中起着关键作用,并在跨平台开发和国际化方面提供支持。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

320

2023.12.20

java多态详细介绍
java多态详细介绍

本专题整合了java多态相关内容,阅读专题下面的文章了解更多详细内容。

355

2025.11.27

C++ 智能指针与现代内存管理
C++ 智能指针与现代内存管理

深入讲解 C++ 现代内存管理的核心工具——智能指针,涵盖 unique_ptr 独占所有权语义、shared_ptr 引用计数机制与循环引用问题、weak_ptr 弱引用的应用场景、make_unique/make_shared 工厂函数的性能优势、自定义删除器的编写、RAII 资源管理思想的实践,以及从裸指针迁移到智能指针的重构策略,帮助开发者编写安全无泄漏的现代 C++ 代码。

359

2026.04.23

treenode的用法
treenode的用法

​在计算机编程领域,TreeNode是一种常见的数据结构,通常用于构建树形结构。在不同的编程语言中,TreeNode可能有不同的实现方式和用法,通常用于表示树的节点信息。更多关于treenode相关问题详情请看本专题下面的文章。php中文网欢迎大家前来学习。

2621

2023.12.01

C++ 高效算法与数据结构
C++ 高效算法与数据结构

本专题讲解 C++ 中常用算法与数据结构的实现与优化,涵盖排序算法(快速排序、归并排序)、查找算法、图算法、动态规划、贪心算法等,并结合实际案例分析如何选择最优算法来提高程序效率。通过深入理解数据结构(链表、树、堆、哈希表等),帮助开发者提升 在复杂应用中的算法设计与性能优化能力。

356

2025.12.22

深入理解算法:高效算法与数据结构专题
深入理解算法:高效算法与数据结构专题

本专题专注于算法与数据结构的核心概念,适合想深入理解并提升编程能力的开发者。专题内容包括常见数据结构的实现与应用,如数组、链表、栈、队列、哈希表、树、图等;以及高效的排序算法、搜索算法、动态规划等经典算法。通过详细的讲解与复杂度分析,帮助开发者不仅能熟练运用这些基础知识,还能在实际编程中优化性能,提高代码的执行效率。本专题适合准备面试的开发者,也适合希望提高算法思维的编程爱好者。

397

2026.01.06

C++ 数据结构与算法实现教程合集
C++ 数据结构与算法实现教程合集

以 C++ 为实现语言,系统讲解核心数据结构与算法,涵盖链表(单链表/双链表/环检测)、栈与队列(单调栈/优先队列)、二叉树(遍历/BST/AVL/红黑树)、哈希表(开地址法/链地址法)、图(邻接表/BFS/DFS/Dijkstra/拓扑排序)、常见排序算法(快排/归并/堆排/计数排序)的实现与复杂度分析,同时分享 LeetCode 刷题技巧、竞赛编程常用模板(二分/前缀和/滑动窗口/动态规划),帮助开发者夯实算法基础。

452

2026.05.09

堆和栈的区别
堆和栈的区别

堆和栈的区别:1、内存分配方式不同;2、大小不同;3、数据访问方式不同;4、数据的生命周期。本专题为大家提供堆和栈的区别的相关的文章、下载、课程内容,供大家免费下载体验。

6067

2023.07.18

Gradle插件开发入门教程
Gradle插件开发入门教程

本专题介绍Gradle插件开发的基本流程,涵盖插件类编写、自定义任务注册、扩展参数设计和插件应用方法,帮助你理解插件如何组织构建逻辑,减少多个项目之间的重复配置。

40

2026.10.22

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn