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

news2024/10/7 16:21:14

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:
  explicit
  GPUContext (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:
  explicit
  Device (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.coloradmin.cn/o/1886912.html

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

相关文章

Linux指定文件权限的两种方式-符号与八进制数方式示例

一、指定文件权限可用的两种方式&#xff1a; 对于八进制数指定的方式&#xff0c;文件权限字符代表的有效位设为‘1’&#xff0c;即“rw-”、“rw-”、“r--”&#xff0c;以二进制表示为“110”、“110”、“100”&#xff0c;再转换为八进制6、6、4&#xff0c;所以777代表…

Golang中defer和return顺序

在Golang中&#xff0c;defer 和 return 的执行顺序是一个重要的特性&#xff0c;它们的执行顺序如下&#xff1a; return语句不是一条单独的语句&#xff0c;实际上&#xff0c;它是由赋值和返回两部分组成的。赋值步骤会先执行&#xff0c;这一步会计算return语句中的表达式…

【YOLOv5进阶】——引入注意力机制-以SE为例

声明&#xff1a;笔记是做项目时根据B站博主视频学习时自己编写&#xff0c;请勿随意转载&#xff01; 一、站在巨人的肩膀上 SE模块即Squeeze-and-Excitation 模块&#xff0c;这是一种常用于卷积神经网络中的注意力机制&#xff01;&#xff01; 借鉴代码的代码链接如下&a…

代码随想录Day69(图论Part05)

并查集 // 1.初始化 int fa[MAXN]; void init(int n) {for (int i1;i<n;i)fa[i]i; }// 2.查询 找到的祖先直接返回&#xff0c;未进行路径压缩 int.find(int i){if(fa[i] i)return i;// 递归出口&#xff0c;当到达了祖先位置&#xff0c;就返回祖先elsereturn find(fa[i])…

大Excel表格76M,电脑16G内存打不开,内存利用率100%虚拟内存占用16G还是卡死提示内存不足,如何才能查看里面内容?

环境: Excel2016 问题描述: 大Excel表格76M,电脑16G内存打不开,内存利用率100%虚拟内存占用16G还是卡死提示内存不足,如何才能查看里面内容? 解决方案: 遇到这种情况,说明Excel文件非常大,超出了你当前计算机配置的处理能力。以下是一些解决方法,帮助你尝试打开或…

【Arduino】XIAOFEIYU实验ESP32使用TOUCH触摸模块(图文)

今天XIAOFEIYU继续来实验ESP32使用传感器模块&#xff0c;这次用到的模块为TOUCH触摸模块。 三个针脚分别为正负极&#xff0c;IO针脚。 #define pin 25void setup(){Serial.begin(9600); pinMode(pin, INPUT); }float value 0.0; void loop(){value digitalRead(pin); …

Andrej Karpathy提出未来计算机2.0构想: 完全由神经网络驱动!网友炸锅了

昨天凌晨&#xff0c;知名人工智能专家、OpenAI的联合创始人Andrej Karpathy提出了一个革命性的未来计算机的构想&#xff1a;完全由神经网络驱动的计算机&#xff0c;不再依赖传统的软件代码。 嗯&#xff0c;这是什么意思&#xff1f;全部原生LLM硬件设备的意思吗&#xff1f…

编译原理3-自底向上的语法分析

自底向上分析 &#xff0c;就是自左至右扫描输入串&#xff0c;自底向上进 行分析&#xff1b;通过反复查找当前句型的 句柄&#xff0c; 并使 用产生式规则 将找到的句柄归约为相应的非终结符 。逐步进行“ 归约 ”&#xff0c;直到至文法的开始符号&#xff1b; 对于规范推导…

详解反向传播(BP)算法

文章目录 what&#xff08;是什么&#xff09;where&#xff08;用在哪&#xff09;How&#xff08;原理&&怎么用&#xff09;原理以及推导过程pytorch中的反向传播 what&#xff08;是什么&#xff09; 反向传播算法&#xff08;Backpropagation&#xff09;是一种用于…

为什么是视频传输用YUV格式,而放弃RGB格式?

&#x1f60e; 作者介绍&#xff1a;我是程序员行者孙&#xff0c;一个热爱分享技术的制能工人。计算机本硕&#xff0c;人工制能研究生。公众号&#xff1a;AI Sun&#xff0c;视频号&#xff1a;AI-行者Sun &#x1f388; 本文专栏&#xff1a;本文收录于《音视频》系列专栏&…

如何寻找一个领域的顶级会议,并且判断这个会议的影响力?

如何寻找一个领域的顶级会议&#xff0c;并且判断这个会议的影响力&#xff1f; 会议之眼 快讯 很多同学都在问&#xff1a;学术会议不是期刊&#xff0c;即使被SCI检索&#xff0c;也无法查询影响因子。那么如何知道各个领域的顶级会议&#xff0c;并对各个会议有初步了解呢…

Qt加载SVG矢量图片,放大缩小图片质量不发生变化。

前言&#xff1a; 首先简单描述下SVG: SVG 意为可缩放矢量图形&#xff08;Scalable Vector Graphics&#xff09;。 SVG 使用 XML 格式定义图像。 给界面或者按钮上显示一个图标或背景图片&#xff0c;日常使用.png格式的文件完全够用&#xff0c;但是有些使用场景需要把图…

代码随想录第41天|动态规划

322. 零钱兑换 dp[j] : 最小硬币数量, j 为金额(相当于背包空间)递推公式 : dp[j] min(dp[j - coins[i]] 1, dp[j])初始化: 需要一个最大值, 避免覆盖, dp[0] 0遍历顺序: 钱币有序无序不影响, 因为求解最小个数, 结果相同(先遍历物品后背包, 先背包后物品都可) class Solut…

NSSCTF-Web题目21(文件上传-phar协议、RCE-空格绕过)

目录 [NISACTF 2022]bingdundun~ 1、题目 2、知识点 3、思路 [FSCTF 2023]细狗2.0 4、题目 5、知识点 6、思路 [NISACTF 2022]bingdundun~ 1、题目 2、知识点 文件上传&#xff0c;phar伪协议 3、思路 点击upload&#xff0c;看看 这里提示我们可以上传图片或压缩包&…

Nginx主配置文件---Nginx.conf

nginx主配置文件的模块介绍 全局块&#xff1a; 全局块是配置文件从开始到 events 块之间的部分&#xff0c;其中指令的作用域是 Nginx 服务器全局。主要指令包括&#xff1a; user&#xff1a;指定可以运行 Nginx 服务的用户和用户组&#xff0c;只能在全局块配置。例如&…

Linux多线程【线程互斥】

文章目录 Linux线程互斥进程线程间的互斥相关背景概念互斥量mutex模拟抢票代码 互斥量的接口初始化互斥量销毁互斥量互斥量加锁和解锁改进模拟抢票代码&#xff08;加锁&#xff09;小结对锁封装 lockGuard.hpp 互斥量实现原理探究可重入VS线程安全概念常见的线程不安全的情况常…

【开发环境】MacBook M系列芯片环境下搭建完整Python开发环境

文章目录 Anaconda和Python的关系&#xff1f;1. Python2. Anaconda 安装AnacondaPycharm整合Anaconda运行你的Python代码 Anaconda和Python的关系&#xff1f; 如果有简单了解过Python语言的&#xff0c;那么你很容易就会听到有人会叫你安装Anaconda。 那么Anaconda是什么&am…

编译原理2

推导和短语 推导 推导过程中&#xff0c;每一步推导都是对句型的 最右非终结符 进行替换&#xff0c;最右推导(规范推导)&#xff1b; 短语 用 β 替换 A&#xff0c;则 β 就是 关于A 的一个短语&#xff1b; 直接短语是短语范围内的一步推导&#xff1b; 直接短语可能不…

Rust学习笔记007:Trait --- Rust的“接口”

Trait 在Rust中&#xff0c;Trait&#xff08;特质&#xff09;是一种定义方法集合的机制&#xff0c;类似于其他编程语言中的接口&#xff08;java&#xff09;或抽象类(c的虚函数)。 。Trait 告诉 Rust 编译器: 某种类型具有哪些并且可以与其它类型共享的功能Trait:抽象的…

[ROS 系列学习教程] 建模与仿真 - 使用 ros_control 控制差速轮式机器人

ROS 系列学习教程(总目录) 本文目录 一、差速轮式机器人二、差速驱动机器人运动学模型三、对外接口3.1 输入接口3.2 输出接口 四、控制器参数五、配置控制器参数六、编写硬件抽象接口七、控制机器人移动八、源码 ros_control 提供了多种控制器&#xff0c;其中 diff_drive_cont…