C++初学者指南-3.自定义类型(第一部分)-析构函数

C++初学者指南-3.自定义类型(第一部分)-析构函数

文章目录

  • C++初学者指南-3.自定义类型(第一部分)-析构函数
    • 特殊的成员函数
    • 用户定义的构造函数和析构函数
    • RAII
    • 示例:资源处理
    • 示例:RAII记录
    • 零规则

特殊的成员函数

T::T()默认构造函数当创建新的 T 对象时运行。
T::T(param…)特殊构造函数创建带参数的新 T 对象时运行
T::~T()析构函数当现有的 T 对象被销毁时运行

编译器会在我们没有自己定义的情况下生成一个默认构造函数和一个析构函数。
在后面的章节中,我们将了解到四个特殊的成员,可以用来控制类型的复制和移动行为。

  • copy constructor(拷贝构造函数)  T::T(T const&)
  • copy assignment operator(拷贝赋值操作符函数)  T& T::operator = (T const&)
  • move constructor(移动构造函数)  T::T(T &&)
  • move assignment operator (移动赋值操作符函数) T& T::operator = (T &&)

它们通常也是由编译器自动生成的,在许多/大多数情况下不需要用户自定义。

用户定义的构造函数和析构函数

class Point {};
class Test {std::vector<Point> w_;std::vector<int> v_;int i_ = 0;
public:Test() { std::cout << "constructor\n"; }~Test() { std::cout << "destructor\n"; }// more member functions …
};

运行上面代码

if (…) {…Test x;  // prints 'constructor'…
}  // prints 'destructor'

销毁时执行顺序
在析构函数体运行完毕后,所有数据成员的析构函数将按照声明的相反顺序执行。这是自动发生的,不能更改(至少不容易改 - 毕竟这是C++,几乎有可以绕过任何事情的方法)。

x 超出作用域范围→执行 ~Test():

  • std::cout << “destructor\n”;
  • x的数据成员被销毁了:
    • i_ 被销毁了(基本类型没有析构函数)
    • v_ 被销毁 → 执行析构函数 ~vector():
      • vector在其缓冲区中销毁整数元素;(基本类型→没有析构函数)
      • 释放堆上的缓冲区内存
      • v_的剩余数据成员已被销毁
    • w_ 被销毁 → 执行析构函数 ~vector():
      • vector在其缓冲区中销毁Point元素
      • 每个~Point()析构函数都会被执行
      • 释放堆上的缓冲区内存
      • w_的剩余数据成员被销毁

RAII

“资源获取即初始化”

  • 对象构建:获取资源
  • 对象销毁:释放资源

示例:std::vector

  • 每个向量对象都拥有一个独立的堆上缓冲区,在那里存储着实际内容。
  • 该缓冲区是根据需要分配的,并且在向量对象被销毁时被释放。
    在这里插入图片描述

所有权
如果一个对象负责资源的生命周期(初始化/创建、终结/销毁),我们就说它是资源(内存、文件句柄、连接、线程、锁……)的所有者。

提醒:C++ 使用值语义
= 变量指向对象本身,而不仅仅是引用/指针。
这是几乎所有编程语言中基本类型(int、double等)的默认行为,也是C++中用户自定义类型的默认行为:

  • 深拷贝:生成一个新的、独立的对象;对象(成员)的值被复制
  • 深层赋值:使目标的值等于源对象的值
  • 深层所有权:成员变量指向与包含对象具有相同生命周期的对象
  • 基于值的比较:如果它们的数值相等/较小,则变量进行相等/小于/… 的比较。

由于成员的生命周期与其包含的对象绑定在一起,所以不需要垃圾回收器。

示例:资源处理

常见情况
我们需要使用一个外部的 © 库,它具有自己的资源管理。这些资源可以是内存,还可以是设备、网络连接、已打开的文件等。
在这样的库中,资源通常是通过初始化和清理函数来处理的,比如 lib_init() 和 lib_finalize() ,用户需要调用这些函数。
问题:资源泄漏
通常在程序庞大且控制流复杂时,经常会忘记调用最终清理函数。这可能导致设备卡住,内存未被释放等问题。
解决方案:RAII 包装器

  • 在构造函数中调用初始化函数
  • 在析构函数中调用清理函数
  • 额外优势:包装类还可以用来存储上下文信息,如连接详情,设备ID等,这些只在初始化和结束之间有效
  • 这样的包装器大多数情况下应该是不可复制的,因为它处理着独特的资源(在后面的章节中会有更详细的解释)
#include <gpulib.h>class GPUContext {int gpuid_;
public:explicitGPUContext (int gpuid = 0): gpuid_{gpuid} {gpulib_init(gpuid_);}~GPUContext () {gpulib_finalize(gpuid_);}[[nodiscard]] int gpu_id () const noexcept { return gpuid_;}
// make non-copyable:GPUContext (GPUContext const&) = delete;GPUContext& operator = (GPUContext const&) = delete;
};int main () {if () {// 创建和初始化上下文GPUContext gpu;// 在这里处理事情} // 自动清理释放!}

示例:RAII记录

  • Device的构造函数获得一个指向UsageLog对象的指针
  • UsageLog 可以用来记录 Device 对象生命周期中的操作
  • 如果Device不再存在,析构函数会通知UsageLog
  • UsageLog 还可以统计活跃设备的数量等等
class File {};
class DeviceID {};class UsageLog {
public:explicit UsageLog (File const&);void armed (DeviceID);void disarmed (DeviceID);void fired (DeviceID);
};class Device {DeviceID id_;UsageLog* log_;public:explicitDevice (DeviceId id, UsageLog* log = nullptr): id_{id}, log_{log},{ if (log_) log_->armed(id_);}~Device () { if (log_) log_->disarmed(id_); }void fire () {if (log_) log_->fired(id_);}};int main () {File file {"log.txt"}UsageLog log {file};…Device d1 {DeviceID{1}, &log};d1.fire(); {Device d2 {DeviceID{2}, &log};d2.fire(); }d1.fire(); 
}
log.txt
device 1   armed
device 1   fired
device 2   armed
device 2   fired
device 2   disarmed
device 1   fired
device 1   disarmed

零规则

= 尽量不要编写特殊成员函数

除非你需要进行 RAII 风格的资源管理或基于生命周期的跟踪,否则请避免编写特殊成员函数。
大多数情况下,编译器生成的默认构造函数和析构函数已经足够了。

初始化并不总是需要编写构造函数。
大多数数据成员可以使用成员初始化器进行初始化。

不要为类型添加空析构函数!
用户自定义析构函数的存在会阻止许多优化,并严重影响性能!

你几乎不需要写析构函数。
在C++11之前,使用自定义类并进行显式手动内存管理是非常常见的。然而,在现代C++中,内存管理策略大多数情况下(也应该)封装在专用类(容器,智能指针,分配器等)中。

附上原文地址
如果文章对您有用,请随手点个赞,谢谢!^_^

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/864903.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

电脑录音方法:电脑怎么录音?5招轻松搞定录音!

想要从麦克风或系统音频录制电脑声音吗&#xff1f;这是一项简单的任务。本文将为您介绍5种最佳且最简单的方法&#xff0c;包括使用Windows系统自带的录音工具来录制电脑音频&#xff0c;在线音频录音软件和专业的第三方电脑录音软件。这些工具都能够很好地帮助您完成电脑怎么…

一款十六进制编辑器,你的瑞士军刀!!【送源码】

软件介绍 ImHex是一款功能强大的十六进制编辑器&#xff0c;专为逆向工程师、程序员以及夜间工作的用户设计。它不仅提供了基础的二进制数据编辑功能&#xff0c;还集成了一系列高级特性&#xff0c;使其成为分析和修改二进制文件的理想工具。 功能特点 专为逆向工程、编程和夜…

verilog实现PID控制

1 原理讲解 距离上一次说PID算法的事情过去蛮久了&#xff0c;今天又重新看了看PID的代码&#xff0c;其实还是存在一些不合理的地方。 整理归纳了一下原理&#xff0c;位置式和增量式的变化。 2 工程实现 timescale 1ns / 1psmodule pid_controller(input clk,input r…

【ARM系列】GIC600AE功能安全

GIC600AE在原GIC600版本基础上增加了FuSa功能&#xff0c;所增加的FuSa特性都集成在GIC600外围&#xff0c;不会改变原GIC600的功能。 GIC600AE主要安全机制分布图&#xff1a; GIC-600AE包含以下FuSa安全机制&#xff1a; lockstep logic protection 通过添加duplication l…

C++ 类与对象(中)

C 类与对象&#xff08;中&#xff09; 1. 类的6个默认成员函数2. 构造函数2.1 概念2.2 特性 3.析构函数3.1 概念3.2 特性 4. 拷贝构造函数4.1 概念4.2 特征 5.赋值运算符重载5.1 运算符重载5.2 赋值运算符重载 1. 类的6个默认成员函数 如果一个类中什么成员都没有&#xff0c…

数据库操作-DML和DQL

DML DML英文全称是Data Manipulation Language(数据操作语言)&#xff0c;用来对数据库中表的数据记录进行增、删、改操作。 添加数据&#xff08;INSERT&#xff09; 1.指定字段添加数据&#xff1a; insert into 表名 ( 字段名 1, 字段名 2) values ( 值 1, 值 2); 2…

【面试题】TLS和SSL协议的区别

TLS&#xff08;Transport Layer Security&#xff09;和SSL&#xff08;Secure Sockets Layer&#xff09;协议都是用于在网络上建立安全通信连接的协议&#xff0c;但它们在多个方面存在区别。以下是TLS和SSL协议之间区别的详细分析&#xff1a; 1. 发展历程与标准化 SSL&a…

企业本地大模型用Ollama+Open WebUI+Stable Diffusion可视化问答及画图

最近在尝试搭建公司内部用户的大模型&#xff0c;可视化回答&#xff0c;并让它能画图出来&#xff0c; 主要包括四块&#xff1a; Ollama 管理和下载各个模型的工具Open WebUI 友好的对话界面Stable Diffusion 绘图工具Docker 部署在容器里&#xff0c;提高效率 以上运行环境…

20240627构造专题

写在前面&#xff1a;出场即巅峰&#xff08;明日模拟赛RP&#xff09; 一.何为构造 就是通过对一道题题面的分析可以发现某种规律&#xff08;类似于不完全归纳法&#xff09;&#xff0c;然后发掘本质&#xff0c;就可以很快的解题&#xff0c;但是显然我还没有掌握 二.一…

基于星火大模型的群聊对话分角色要素提取挑战赛Task1笔记

基于星火大模型的群聊对话分角色要素提取挑战赛Task1笔记 跑通baseline 1、安装依赖 下载相应的数据库 !pip install --upgrade -q spark_ai_python2、配置导入 导入必要的包。 from sparkai.llm.llm import ChatSparkLLM, ChunkPrintHandler from sparkai.core.messages…

操作系统大题复习

磁盘调度算法 一次磁盘读写需要的时间 寻道时间 先来先服务FCFS 优点&#xff1a;公平 缺点&#xff1a;性能差&#xff0c;寻道时间长 最短寻道时间有限SSTF 缺点&#xff1a;可能产生饥饿现象 扫描算法SCAN 优点&#xff1a;不会产生饥饿现象 缺点&#xff1a;响应频率…

Node.js学习(一)

Node.js安装与入门案例&#xff1a; 需求&#xff1a;点击按钮&#xff0c;请求本地目录指定文件的内容&#xff0c;并显示在页面上 刚入门肯定想着直接写相对路径请求指定路径数据就行了&#xff0c;可是会发现不行。 网页运行在浏览器端&#xff0c;通常后续要发布&#xf…

docker部署wg-easy和firefly

Background WireGuard是一种新型的VPN协议,它通过在内核层运行,提供高效、安全、简单和现代的VPN解决方案。wg-easy是一个专为简化 WireGuard VPN配置和管理而设计的工具&#xff0c;提供了界面化的管理&#xff0c;进一步降低WireGuard 的使用门槛&#xff0c;让用户无需深入了…

【C++】STL-stack_queue

目录 1、stack和queue的使用 1.1 最小栈 1.2 栈的弹出和压入序列 1.3 二叉树的层序遍历 2、stack和queue的模拟实现 2.1 适配器 2.2 deque 2.2.1 deque的成员变量 2.2.2 deque的迭代器 2.2.3 deque尾插元素 2.2.4 deque头插元素 2.2.5 下标访问 2.2.6 deque的不足 …

golang结合neo4j实现权限功能设计

neo4j 是非关系型数据库之图形数据库&#xff0c;这里不再赘述。 传统关系数据库基于rbac实现权限, user ---- role ------permission,加上中间表共5张表。 如果再添上部门的概念&#xff1a;用户属于部门&#xff0c;部门拥有 角色&#xff0c;则又多了一层&#xff1a; user-…

数据加密解密和哈希的解析

[S1301]数据的加解密 对提供的原始数据&#xff08;字符串或者二进制数组&#xff09;进行加密是数据保护框架体提供的基本功能&#xff0c;接下来我们利用一个简单的控制台程序来演示一下加解密如何实现。数据的加解密均由IDataProtector对象来完成&#xff0c;而该对象由IDa…

Elasticsearch:Runtime fields - 运行时字段(一)

运行时字段&#xff08;runtime fields&#xff09;是在查询时计算的字段。运行时字段使你能够&#xff1a; 向现有文档添加字段而无需重新索引数据开始处理数据而无需了解其结构在查询时覆盖索引字段返回的值定义用于特定用途的字段而无需修改底层架构 你可以像访问其他任何…

bash条件判断基础adsawq1`1nn

判断的作用 判断后续操作的提前条件是否满足如果满足执行一种命令不满足则执行另一种指令 条件测试类型&#xff1a; 整型测试字符测试文字测试 整数测试&#xff1a;比较两个整数谁大谁小&#xff0c;是否相等&#xff1b; 二元测试&#xff1a; num1 操作符 num2 -eq: 等于…

uniapp封装虚拟列表滚动组件

uniapp封装虚拟列表滚动组件 这里用到一个列表&#xff0c;然后数据可能有很多很多…&#xff0c;一次性全部渲染到dom上会卡顿&#xff0c;很废性能&#xff0c;于是用了这个虚拟列表就变丝滑很多很多。 组件mosoweInventedList 代码&#xff1a; <!-- 虚拟滚动列表组件&a…

代码随想录Day67(图论 part04)

110.字符串接龙 题目&#xff1a;110. 字符串接龙 (kamacoder.com) 思路&#xff1a;没有思路 答案 import java.util.*;public class Main {public static void main(String[] args) {Scanner scanner new Scanner(System.in);int n scanner.nextInt();String beginStr sc…