当前位置: 首页 > news >正文

类的特殊成员函数——三之法则、五之法则、零之法则

        系统中的动态资源、文件句柄(socket描述符、文件描述符)是有限的,在类中若涉及对此类资源的操作,但是未做到妥善的管理,常会造成资源泄露问题,严重的可能造成资源不可用。或引发未定义行为,进而引起程序崩溃、表现出意外的行为、损坏数据,或者可能看似正常工作但在其它情况下出现问题。

        三之法则和五之法则可以很好地解决上述问题。它们帮助开发者理解和管理类的拷贝控制操作,避免常见的资源泄露、重复释放等问题,并优化程序的性能。零之法则关注类的特殊成员函数的声明和使用。在实际开发中,应根据类的具体需求来决定是否需要自定义这些特殊的成员函数。

一、三之法则

1、概念

        三之法则,也称为“三大定律”或“三法则”,它指出,如果类定义了以下三个特殊成员函数之一:析构函数、拷贝构造函数或拷贝赋值运算符,则开发者通常也需要定义其它两个特殊成员函数,以确保类的拷贝控制和资源管理行为的正确性。

2、使用场景

        三之法则主要是为了避免资源泄露、重复释放或其它由于浅拷贝导致的错误。

        默认情况下,编译器会为类生成默认的析构函数、拷贝构造函数和拷贝赋值运算符,但这些默认实现通常只进行浅拷贝,即只复制对象的成员变量的值,而不复制成员变量所指向的资源。如果成员变量是指针,并且指向动态分配的内存,则浅拷贝会导致两个对象共享同一块内存,从而在销毁时发生重复释放的错误。

3、如何实现

定义所有需要的特殊成员函数:如果类需要自定义其中一个特殊成员函数,那么通常也需要自定义其他两个成员函数,以确保对象的拷贝和赋值行为符合预期。

理解资源管理:了解类所管理的资源,并决定是否需要自定义特殊成员函数来管理这些资源的拷贝和赋值。

使用RAII:将资源的生命周期与对象的生命周期绑定,简化资源管理,降低资源泄露风险。

4、示例

4.1 类中包含指针私有成员

#include <iostream>
#include <cstring>class Point {  
public:  Point(size_t n = 0) : numCoords(n), coords(n ? new double[n] : nullptr){if (coords) {std::memset(coords, 0, n * sizeof(double));}}~Point(){delete[] coords;}Point(const Point &other) : numCoords(other.numCoords), coords(new double[other.numCoords]){std::memcpy(coords, other.coords, numCoords * sizeof(double));}Point &operator=(const Point &other){  if (this != &other) {delete[] coords;numCoords = other.numCoords;coords = new double[numCoords];std::memcpy(coords, other.coords, numCoords * sizeof(double));}return *this;}  private:  double *coords; size_t numCoords;
};  int main() 
{Point p1(3);Point p2 = p1;Point p3;p3 = p1;return 0;
}

二、五之法则

1、概念

        五之法则在C++11及以后版本引入,它在三之法则的基础上增加了两个新的特殊成员函数:移动构造函数和移动赋值运算符,以支持移动语义。

2、使用场景

        五之法则的引入是为了进一步提高程序的性能,特别是在处理大型对象或资源密集型对象时。通过允许对象之间的资源移动而不是复制,可以减少不必要的内存分配和释放操作,从而提高程序的运行效率。

3、如何实现

定义所有五个特殊成员函数:如果类需要移动语义,则应该定义所有五个特殊成员函数(析构函数、拷贝构造函数、拷贝赋值运算符、移动构造函数和移动赋值运算符)。

使用noexcept关键字:在C++11及以后版本中,移动构造函数和移动赋值运算符通常会被标记为noexcept,表明它们不会抛出异常。这有助于编译器优化代码,并允许在更多情况下使用移动语义。

理解移动语义:了解移动语义的工作原理,并决定何时以及如何使用它来优化程序的性能。

4、示例

4.1 socket描述符

#include <iostream>
#include <cstdio>
#include <cstring>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <stdexcept>class SocketDescriptor {
public:// 默认构造函数,创建一个无效的socketSocketDescriptor() : fd(-1) {}// 构造函数,接受一个已创建的socket文件描述符explicit SocketDescriptor(int socket_fd) : fd(socket_fd){if (fd == -1) {throw std::runtime_error("Invalid socket descriptor");}}// 析构函数,关闭socket文件描述符  ~SocketDescriptor(){closeSocket();}// 禁用拷贝构造函数SocketDescriptor(const SocketDescriptor&) = delete;// 禁用拷贝赋值运算符SocketDescriptor& operator=(const SocketDescriptor&) = delete; // 移动构造函数SocketDescriptor(SocketDescriptor&& other) noexcept : fd(other.fd){other.fd = -1; // 将原对象的fd置为无效}// 移动赋值运算符SocketDescriptor& operator=(SocketDescriptor&& other) noexcept{if (this != &other) {closeSocket(); // 关闭当前对象的fdfd = other.fd; // 转移资源other.fd = -1; // 将原对象的fd置为无效}return *this;}// 获取socket文件描述符int get() const{return fd;}// 创建一个新的socket并管理它static SocketDescriptor createSocket(int domain, int type, int protocol){int fd = socket(domain, type, protocol);if (fd == -1) {throw std::runtime_error("Failed to create socket");}return SocketDescriptor(fd);}// 关闭socket文件描述符
private:void closeSocket(){if (fd != -1) {::close(fd);fd = -1;}}private:int fd;
};int main()
{try {// 创建一个新的TCP socketSocketDescriptor sock = SocketDescriptor::createSocket(AF_INET, SOCK_STREAM, 0);// 打印socket文件描述符std::cout << "Socket descriptor: " << sock.get() << std::endl; } catch (const std::exception& e) {std::cerr << "Error: " << e.what() << std::endl;}return 0;
}

4.2 拷贝"大"数据

#include <iostream>
#include <algorithm> // 用于std::copy
#include <stdexcept> // 用于std::runtime_error 和 std::bad_alloc 的异常处理class LargeData {
public:LargeData() = default;// 构造函数,接受数据大小并初始化数据explicit LargeData(size_t dataSize){try {data = new char[dataSize];this->dataSize = dataSize;std::fill_n(data, dataSize, 0); // 初始化数据为0(可选)} catch (const std::bad_alloc&) {throw std::runtime_error("Memory allocation failed in LargeData constructor");}}// 析构函数,释放动态分配的数据~LargeData(){delete[] data;}// 拷贝构造函数,执行深拷贝LargeData(const LargeData& other) : dataSize(other.dataSize){try {data = new char[dataSize];std::copy(other.data, other.data + dataSize, data);} catch (const std::bad_alloc&) {throw std::runtime_error("Memory allocation failed in LargeData copy constructor");}}// 拷贝赋值运算符,执行深拷贝LargeData& operator=(const LargeData& other){if (this == &other) {return *this; // 自赋值检查}// 释放当前对象的资源char* oldData = data;// 分配新资源并复制数据try {dataSize = other.dataSize;data = new char[dataSize];std::copy(other.data, other.data + dataSize, data);} catch (const std::bad_alloc&) {// 如果新分配失败,恢复旧状态并抛出异常dataSize = 0; // 或者保持原大小,但这可能会导致不一致状态data = oldData; // 这里实际上不应该这样做,因为oldData可能已经被释放或指向无效内存throw std::runtime_error("Memory allocation failed in LargeData copy assignment operator");}return *this;}// 移动构造函数,接管资源(之前已经正确实现)LargeData(LargeData&& other) noexcept : dataSize(0), data(nullptr){*this = std::move(other); // 委托给移动赋值运算符}// 移动赋值运算符,接管资源并释放旧数据(之前已经正确实现)LargeData& operator=(LargeData&& other) noexcept{if (this == &other) {return *this; // 自赋值检查}// 释放当前对象的资源delete[] data;// 接管其他对象的资源data = other.data;dataSize = other.dataSize;// 重置其他对象的资源指针以避免悬挂指针other.data = nullptr;other.dataSize = 0;return *this;}// 获取数据大小size_t getSize() const{return dataSize;}// 获取数据指针(注意:返回的指针不应被删除)const char* getData() const{return data;}// 非const版本的getData,用于修改数据(通常不推荐这样做,但为了完整性而提供)char* getData(){return data;}// 设置数据(示例函数,用于修改数据内容,注意大小必须匹配)void setData(const char* newData, size_t newSize){if (newSize != dataSize) {throw std::runtime_error("New data size does not match existing data size");}std::copy(newData, newData + newSize, data);}private:size_t dataSize; // 数据大小char* data;      // 指向动态分配数据的指针
};int main()
{try {// 创建大量数据对象LargeData data1(1024); // 1KB数据// 使用拷贝构造函数LargeData data2 = data1; // data2是data1的深拷贝// 使用拷贝赋值运算符LargeData data3;data3 = data1; // data3现在是data1的深拷贝// 清理资源(由析构函数自动处理)} catch (const std::exception& e) {std::cerr << "Exception: " << e.what() << std::endl;}return 0;
}

三、零之法则

1、概念

        C++的零之法则是指,如果可能,类应该避免声明任何特殊成员函数。鼓励让编译器自动生成这些特殊成员函数,以简化类的设计和管理。

2、使用场景

简化设计:零之法则通过减少需要编写的代码量,简化类的设计。当类不需要显式管理资源时,遵循零之法则可以使类的接口更加清晰。

减少错误:手动编写特殊成员函数容易引入错误,特别是当类的成员变量较多或类型复杂时。编译器生成的特殊成员函数通常更加健壮。

利用标准库:零之法则鼓励使用标准库组件(如std::string、std::vector等)来管理资源。

提高可维护性:遵循零之法则的类更加简洁,更易于理解和维护。

3、如何实现

避免显式声明特殊成员函数:除非类需要显式管理资源,否则让编译器自动生成这些函数。

使用组合而非继承:组合优于继承是面向对象设计中的一个重要原则。通过组合,可以将其它类的实例作为当前类的成员变量,从而避免复杂的继承关系和虚函数的开销。

利用智能指针:对于需要动态分配内存的场景,使用C++11及以后版本中引入的智能指针(如std::unique_ptr、std::shared_ptr等)。这些智能指针可以自动管理内存,减少内存泄漏的风险。

4、示例

4.1 合理使用C++标准库管理内存

#include <iostream>
#include <stdexcept>
#include <string>class LargeData {
public:// 默认构造函数,创建一个空的LargeData对象LargeData() = default;// 构造函数,接受数据大小(以字节为单位)并初始化一个相应大小的空字符串explicit LargeData(size_t dataSize) : data(dataSize, '\0') {}// 获取数据大小(以字节为单位)size_t getSize() const{return data.size();}// 获取数据(返回const char*以避免修改数据)const char* getData() const{return data.c_str();}// 非const版本的getData,用于修改数据(通常不推荐这样做,但为了完整性而提供)// 注意:这允许调用者修改数据,但他们必须确保不超出字符串的边界。char* getData() {return &data[0];}// 设置数据(注意:大小必须匹配,否则抛出异常)void setData(const char* newData, size_t newSize){if (newSize != data.size()) {throw std::runtime_error("New data size does not match existing data size");}data.assign(newData, newSize);}// 为了方便,添加一个接受std::string的setData重载void setData(const std::string& newData){if (newData.size() != data.size()) {throw std::runtime_error("New data size does not match existing data size");}data = newData;}private:std::string data; // 使用std::string来存储数据
};int main() 
{try {// 创建LargeData对象,大小为1024字节LargeData data1(1024);// 使用拷贝构造函数(由编译器隐式生成)LargeData data2 = data1; // data2是data1的一个深拷贝(因为std::string是深拷贝的)// 使用拷贝赋值运算符(由编译器隐式生成)LargeData data3;data3 = data1; // data3现在是data1的一个深拷贝// 注意:不需要手动清理资源,因为std::string会处理它} catch (const std::exception& e) {std::cerr << "Exception: " << e.what() << std::endl;}return 0;
}

4.2 智能指针管理资源

#include <iostream>
#include <memory>
#include <vector>// 一个简单的类,表示资源
class Resource {
public:Resource(int value) : value_(value){std::cout << "Resource created with value: " << value_ << std::endl;}~Resource(){std::cout << "Resource destroyed with value: " << value_ << std::endl;}void display() const{std::cout << "Resource value: " << value_ << std::endl;}
private:int value_;// 禁止拷贝和赋值Resource(const Resource&) = delete;Resource& operator=(const Resource&) = delete;
};// 一个管理类,使用智能指针管理资源
class ResourceManager {
public:void addResource(int value){// 使用std::make_unique来创建unique_ptrresources_.push_back(std::make_unique<Resource>(value));}void displayAllResources() const{for (const auto& resource : resources_) {resource->display();}}private:std::vector<std::unique_ptr<Resource>> resources_;
};int main()
{// 创建ResourceManager对象ResourceManager manager;// 添加资源manager.addResource(10);manager.addResource(20);manager.addResource(30);// 显示所有资源manager.displayAllResources();// ResourceManager对象离开作用域时,其析构函数会调用,所有unique_ptr会自动释放资源return 0;
}

相关文章:

类的特殊成员函数——三之法则、五之法则、零之法则

系统中的动态资源、文件句柄&#xff08;socket描述符、文件描述符&#xff09;是有限的&#xff0c;在类中若涉及对此类资源的操作&#xff0c;但是未做到妥善的管理&#xff0c;常会造成资源泄露问题&#xff0c;严重的可能造成资源不可用。或引发未定义行为&#xff0c;进而…...

计算机毕业设计 智慧物业服务系统的设计与实现 Java实战项目 附源码+文档+视频讲解

博主介绍&#xff1a;✌从事软件开发10年之余&#xff0c;专注于Java技术领域、Python人工智能及数据挖掘、小程序项目开发和Android项目开发等。CSDN、掘金、华为云、InfoQ、阿里云等平台优质作者✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精…...

Python软体中使用SpaCy进行命名实体识别

Python软体中使用SpaCy进行命名实体识别 命名实体识别(Named Entity Recognition,NER)是自然语言处理(NLP)中的一个重要任务,它涉及识别文本中的命名实体,例如人名、地名、组织名等。SpaCy是一种流行的NLP库,提供了高效的NER功能。在本文中,我们将介绍如何使用SpaCy进…...

华为云技术深度解析:以系统性创新加速智能化升级

华为云技术深度解析&#xff1a;以系统性创新加速智能化升级 在当今数字化转型的浪潮中&#xff0c;云计算作为关键的基础设施&#xff0c;正以前所未有的速度推动着各行各业的智能化升级。作为全球领先的云服务提供商&#xff0c;华为云凭借其深厚的技术积累和创新实力&#…...

推理攻击-Python案例

1、本文通过推理攻击的方式来估计训练集中每个类别的样本数量、某样本是否在训练集中。 2、一种简单的实现方法&#xff1a;用模型对训练数据标签进行拟合&#xff0c;拟合结果即推理为训练集中的情况。 3、了解这些案例可以帮助我们更好的保护数据隐私。 推理攻击&#xff08;…...

find_box_3d

参数 &#xff08;ObjectModel3DScene, SideLen1, SideLen2, SideLen3, MinScore, GenParam : GrippingPose, Score, ObjectModel3DBox, BoxInformation) 入参介绍 1&#xff0c;ObjectModel3DScene&#xff0c; 输入的3d模型&#xff0c;这个模型最好是由xyx三通道点…...

Visual Studio2017编译GDAL3.0.2源码过程

一、编译环境 操作系统&#xff1a;Windows 10企业版 编译工具&#xff1a;Visual Studio 2017旗舰版 源码版本&#xff1a;gdal3.0.2 二、生成解决方案 打开Visual Studio 2017的x64本机生成工具&#xff0c;切换到gdal3.0.2源码根目录&#xff1b;执行generate_vcxproj.b…...

计算机网络——email

pop3拉出来 超出ASCII码范围就不让传了 这样就可以传更大的文件...

【Linux】信号知识三把斧——信号的产生、保存和处理

目录​​​​​​​ 1、关于信号的前置知识 1.1.什么是信号&#xff1f; 1.2.为什么要学习信号&#xff1f; 1.3.如何学习信号&#xff1f; 1.4.一些常见的信号 1.5.信号的处理方式 1.6.为什么每一个进程都可以系统调用&#xff1f; 2.信号的产生 2.1.kill命令产生信号…...

【国庆要来了】基于Leaflet的旅游路线WebGIS可视化实践

前言 转眼2024年的国庆节马上就要来临了&#xff0c;估计很多小伙伴都计划好了旅游路线。金秋十月&#xff0c;不管是选择出门去看看风景&#xff0c;还是选择在家里看人。从自己生活惯了的城市去别人生活惯了的城市&#xff0c;去感受城市烟火、去感受人文风景&#xff0c;为2…...

Element-UI Plus 暗黑主题切换及自定义主题色

1. 暗黑主题切换 在main.js中引入下面文件 import element-plus/theme-chalk/dark/css-vars.css安装 vueuse/core pnpm add vueuse/coreApp.vue 添加下面代码 使用了 useDark() 的页面才会从 localStorage中读取当前主题状态&#xff0c;否则&#xff0c;刷新页面就会恢复默…...

人工智能与机器学习原理精解【31】

文章目录 卷积神经网络CNN定义数学原理与公式计算与定理架构例子例题 全连接层的前馈计算定义数学原理与公式计算过程示例 参考文献 卷积神经网络 CNN 即卷积神经网络&#xff08;Convolutional Neural Networks&#xff09;&#xff0c;是一类包含卷积计算且具有深度结构的前…...

如何安全地大规模部署 GenAI 应用程序

大型语言模型和其他形式的生成式人工智能(GenAI) 的广泛使用带来了许多组织可能没有意识到的安全风险。幸运的是&#xff0c;网络和安全提供商正在寻找方法来应对这些前所未有的威胁。 随着人工智能越来越深入地融入日常业务流程&#xff0c;它面临着泄露专有信息、提供错误答…...

verilog实现FIR滤波系数生成(阶数,FIR滤波器类型及窗函数可调)

在以往采用 FPGA 实现的 FIR 滤波功能&#xff0c;滤波器系数是通过 matlab 计算生成&#xff0c;然后作为固定参数导入到 verilog 程序中&#xff0c;这尽管简单&#xff0c;但灵活性不足。在某些需求下&#xff08;例如捕获任意给定台站信号&#xff09;需要随时修改滤波器的…...

OSPF的不规则区域

1.远离骨干非骨干区域 2.不连续骨干 解决方案 tunnel ---点到点GRE 在合法与非ABR间建立隧道&#xff0c;然后将其宣告于OSPF协议中&#xff1b; 缺点&#xff1a;1、周期和触发信息对中间穿越区域造成资源占用&#xff08;当同一条路由来自不同区域&#xff0c;路由器会先…...

大数据新视界 --大数据大厂之 Ibis:独特架构赋能大数据分析高级抽象层

&#x1f496;&#x1f496;&#x1f496;亲爱的朋友们&#xff0c;热烈欢迎你们来到 青云交的博客&#xff01;能与你们在此邂逅&#xff0c;我满心欢喜&#xff0c;深感无比荣幸。在这个瞬息万变的时代&#xff0c;我们每个人都在苦苦追寻一处能让心灵安然栖息的港湾。而 我的…...

总结TypeScript相关知识

目录 引入认识特点安装使用变量声明类型推导 JS 和 TS 共有类型number类型boolean类型string类型Array类型null和undefined类型object类型symbol类型对象类型函数类型 可选和只读type 和 interface索引签名类型断言非空类型断言类型缩小严格赋值检测现象TS 新增类型字面量类型a…...

pdf怎么编辑修改内容?详细介绍6款pdf编辑器功能

■ pdf怎么编辑修改内容&#xff1f; PDF&#xff08;Portable Document Format&#xff09;作为一种广泛使用的文件格式&#xff0c;具有特点包括兼容性强、易于传输、文件安全性高、跨平台性、可读性强、完整性、可搜索性、安全性、可压缩性。 PDF文件本身是不可以直接进行编…...

【Blender Python】4.获取场景对象的几种方式

概述 有时候我们需要获取场景中已经添加或存在的对象。本节就总结在Blender Python中获取场景中对象的一些方法。 通过名称获取 py.data的objects()方法返回一个对象集合&#xff0c;可以使用键名或者下标形式获取具体的对象。 在默认新建的场景中&#xff0c;存在三个对象…...

鸿蒙harmonyos next flutter通信之EventChannel获取ohos系统时间

建立通道 flutter代码&#xff1a; EventChannel eventChannel EventChannel("com.xmg.eventChannel"); ohos代码&#xff1a; //定义eventChannelprivate eventChannel: EventChannel | null null//定义eventSinkprivate eventSink: EventSink | null null//建…...

Python爬虫实战:研究MechanicalSoup库相关技术

一、MechanicalSoup 库概述 1.1 库简介 MechanicalSoup 是一个 Python 库,专为自动化交互网站而设计。它结合了 requests 的 HTTP 请求能力和 BeautifulSoup 的 HTML 解析能力,提供了直观的 API,让我们可以像人类用户一样浏览网页、填写表单和提交请求。 1.2 主要功能特点…...

动态 Web 开发技术入门篇

一、HTTP 协议核心 1.1 HTTP 基础 协议全称 &#xff1a;HyperText Transfer Protocol&#xff08;超文本传输协议&#xff09; 默认端口 &#xff1a;HTTP 使用 80 端口&#xff0c;HTTPS 使用 443 端口。 请求方法 &#xff1a; GET &#xff1a;用于获取资源&#xff0c;…...

vulnyx Blogger writeup

信息收集 arp-scan nmap 获取userFlag 上web看看 一个默认的页面&#xff0c;gobuster扫一下目录 可以看到扫出的目录中得到了一个有价值的目录/wordpress&#xff0c;说明目标所使用的cms是wordpress&#xff0c;访问http://192.168.43.213/wordpress/然后查看源码能看到 这…...

Python 实现 Web 静态服务器(HTTP 协议)

目录 一、在本地启动 HTTP 服务器1. Windows 下安装 node.js1&#xff09;下载安装包2&#xff09;配置环境变量3&#xff09;安装镜像4&#xff09;node.js 的常用命令 2. 安装 http-server 服务3. 使用 http-server 开启服务1&#xff09;使用 http-server2&#xff09;详解 …...

全面解析数据库:从基础概念到前沿应用​

在数字化时代&#xff0c;数据已成为企业和社会发展的核心资产&#xff0c;而数据库作为存储、管理和处理数据的关键工具&#xff0c;在各个领域发挥着举足轻重的作用。从电商平台的商品信息管理&#xff0c;到社交网络的用户数据存储&#xff0c;再到金融行业的交易记录处理&a…...

对象回调初步研究

_OBJECT_TYPE结构分析 在介绍什么是对象回调前&#xff0c;首先要熟悉下结构 以我们上篇线程回调介绍过的导出的PsProcessType 结构为例&#xff0c;用_OBJECT_TYPE这个结构来解析它&#xff0c;0x80处就是今天要介绍的回调链表&#xff0c;但是先不着急&#xff0c;先把目光…...

字符串哈希+KMP

P10468 兔子与兔子 #include<bits/stdc.h> using namespace std; typedef unsigned long long ull; const int N 1000010; ull a[N], pw[N]; int n; ull gethash(int l, int r){return a[r] - a[l - 1] * pw[r - l 1]; } signed main(){ios::sync_with_stdio(false), …...

leetcode_69.x的平方根

题目如下 &#xff1a; 看到题 &#xff0c;我们最原始的想法就是暴力解决: for(long long i 0;i<INT_MAX;i){if(i*ix){return i;}else if((i*i>x)&&((i-1)*(i-1)<x)){return i-1;}}我们直接开始遍历&#xff0c;我们是整数的平方根&#xff0c;所以我们分两…...

表单设计器拖拽对象时添加属性

背景&#xff1a;因为项目需要。自写设计器。遇到的坑在此记录 使用的拖拽组件时vuedraggable。下面放上局部示例截图。 坑1。draggable标签在拖拽时可以获取到被拖拽的对象属性定义 要使用 :clone, 而不是clone。我想应该是因为draggable标签比较特。另外在使用**:clone时要将…...

【学习记录】使用 Kali Linux 与 Hashcat 进行 WiFi 安全分析:合法的安全测试指南

文章目录 &#x1f4cc; 前言&#x1f9f0; 一、前期准备✅ 安装 Kali Linux✅ 获取支持监听模式的无线网卡 &#x1f6e0; 二、使用 Kali Linux 进行 WiFi 安全测试步骤 1&#xff1a;插入无线网卡并确认识别步骤 2&#xff1a;开启监听模式步骤 3&#xff1a;扫描附近的 WiFi…...