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

《C++ Primer》导学系列:第 13 章 - 拷贝控制

13.1 拷贝、赋值与析构函数

拷贝控制是C++中类设计的重要组成部分,用于管理对象的复制、赋值和销毁过程。理解并正确实现拷贝控制函数(拷贝构造函数、拷贝赋值运算符和析构函数)对于编写健壮和高效的C++程序至关重要。

13.1.1 拷贝构造函数

拷贝构造函数用于创建对象的副本。它的声明形式如下:

ClassName(const ClassName &other);

当对象被复制时,拷贝构造函数会被调用。例如,当对象以值传递的方式作为参数传递或从函数返回时,拷贝构造函数将被调用。

示例代码

#include <iostream>class Example {
public:Example(int v) : value(v) {}Example(const Example &other) : value(other.value) {std::cout << "Copy constructor called" << std::endl;}int getValue() const { return value; }
private:int value;
};void printExample(Example e) {std::cout << "Value: " << e.getValue() << std::endl;
}int main() {Example ex1(10);Example ex2 = ex1;  // 调用拷贝构造函数printExample(ex1);  // 调用拷贝构造函数return 0;
}

13.1.2 拷贝赋值运算符

拷贝赋值运算符用于将一个对象的值赋给另一个对象。它的声明形式如下:

ClassName& operator=(const ClassName &other);

拷贝赋值运算符通常用于对象已经存在的情况下进行赋值操作。

示例代码

#include <iostream>class Example {
public:Example(int v) : value(v) {}Example(const Example &other) : value(other.value) {std::cout << "Copy constructor called" << std::endl;}Example& operator=(const Example &other) {if (this != &other) {value = other.value;std::cout << "Copy assignment operator called" << std::endl;}return *this;}int getValue() const { return value; }
private:int value;
};int main() {Example ex1(10);Example ex2(20);ex2 = ex1;  // 调用拷贝赋值运算符std::cout << "ex2 value: " << ex2.getValue() << std::endl;return 0;
}

13.1.3 析构函数

析构函数用于释放对象使用的资源。它的声明形式如下:

~ClassName();

析构函数在对象的生命周期结束时被自动调用,例如当对象超出作用域或被显式删除时。

示例代码

#include <iostream>class Example {
public:Example(int v) : value(v) {}~Example() {std::cout << "Destructor called" << std::endl;}int getValue() const { return value; }
private:int value;
};int main() {Example ex1(10);std::cout << "ex1 value: " << ex1.getValue() << std::endl;return 0;
}

13.1.4 三/五法则

C++中有一个重要的设计原则,称为“三/五法则”,意思是如果一个类定义了析构函数、拷贝构造函数或拷贝赋值运算符中的一个,那么它很可能需要定义所有这三个函数。随着C++11标准引入移动语义,这个原则扩展为“五法则”,包括移动构造函数和移动赋值运算符。

示例代码

#include <iostream>
#include <utility>class Example {
public:Example(int v) : value(new int(v)) {}Example(const Example &other) : value(new int(*other.value)) {std::cout << "Copy constructor called" << std::endl;}Example& operator=(const Example &other) {if (this != &other) {delete value;value = new int(*other.value);std::cout << "Copy assignment operator called" << std::endl;}return *this;}~Example() {delete value;std::cout << "Destructor called" << std::endl;}// C++11移动构造函数Example(Example &&other) noexcept : value(other.value) {other.value = nullptr;std::cout << "Move constructor called" << std::endl;}// C++11移动赋值运算符Example& operator=(Example &&other) noexcept {if (this != &other) {delete value;value = other.value;other.value = nullptr;std::cout << "Move assignment operator called" << std::endl;}return *this;}int getValue() const { return *value; }
private:int* value;
};int main() {Example ex1(10);Example ex2 = std::move(ex1);  // 调用移动构造函数Example ex3(20);ex3 = std::move(ex2);  // 调用移动赋值运算符return 0;
}

13.1.5 阻止拷贝

在某些情况下,我们可能希望阻止对象被拷贝或赋值。可以通过将拷贝构造函数和拷贝赋值运算符声明为delete来实现这一点。

示例代码

class NonCopyable {
public:NonCopyable() = default;NonCopyable(const NonCopyable &) = delete;NonCopyable& operator=(const NonCopyable &) = delete;
};int main() {NonCopyable obj1;// NonCopyable obj2 = obj1;  // 错误:拷贝构造函数被删除// NonCopyable obj3;// obj3 = obj1;  // 错误:拷贝赋值运算符被删除return 0;
}

重点与难点分析

重点

  1. 拷贝构造函数:了解何时会调用拷贝构造函数,掌握其实现方法。
  2. 拷贝赋值运算符:理解拷贝赋值运算符的使用场景和实现细节。
  3. 析构函数:熟悉析构函数的作用和调用时机。
  4. 三/五法则:掌握何时需要同时实现拷贝构造函数、拷贝赋值运算符和析构函数,理解移动语义下的五法则。
  5. 阻止拷贝:了解如何通过delete关键字阻止对象的拷贝和赋值。

难点

  1. 自我赋值检测:在拷贝赋值运算符中处理自我赋值的情况。
  2. 资源管理:确保在析构函数中正确释放资源,避免内存泄漏和其他资源管理问题。
  3. 编写健壮的拷贝控制函数:理解并正确实现拷贝控制函数,以确保对象在复制、赋值和销毁过程中行为一致且安全。
  4. 实现移动语义:正确实现移动构造函数和移动赋值运算符,提高程序性能。

练习题解析

  1. 练习13.1:编写一个类,包含拷贝构造函数、拷贝赋值运算符和析构函数,测试它们的调用情况。
    • 示例代码
#include <iostream>class Example {
public:Example(int v) : value(v) {}Example(const Example &other) : value(other.value) {std::cout << "Copy constructor called" << std::endl;}Example& operator=(const Example &other) {if (this != &other) {value = other.value;std::cout << "Copy assignment operator called" << std::endl;}return *this;}~Example() {std::cout << "Destructor called" << std::endl;}int getValue() const { return value; }
private:int value;
};int main() {Example ex1(10);Example ex2 = ex1;  // 调用拷贝构造函数ex2 = ex1;          // 调用拷贝赋值运算符return 0;
}
  1. 练习13.2:修改练习13.1的类,使其包含一个指向动态内存的指针,确保拷贝控制函数正确管理该内存。
    • 示例代码
#include <iostream>class Example {
public:
Example(int v) : value(new int(v)) {}
Example(const Example &other) : value(new int(*other.value)) {std::cout << "Copy constructor called" << std::endl;
}
Example& operator=(const Example &other) {if (this != &other) {delete value;value = new int(*other.value);std::cout << "Copy assignment operator called" << std::endl;}return *this;
}
~Example() {delete value;std::cout << "Destructor called" << std::endl;
}
int getValue() const { return *value; }
private:
int* value;
};int main() {Example ex1(10);Example ex2 = ex1;  // 调用拷贝构造函数ex2 = ex1;          // 调用拷贝赋值运算符std::cout << "ex2 value: " << ex2.getValue() << std::endl;return 0;
}
  1. 练习13.3:编写一个类,包含移动构造函数和移动赋值运算符,测试它们的调用情况。
    • 示例代码
#include <iostream>class Example {
public:Example(int v) : value(new int(v)) {}Example(const Example &other) : value(new int(*other.value)) {std::cout << "Copy constructor called" << std::endl;}Example& operator=(const Example &other) {if (this != &other) {delete value;value = new int(*other.value);std::cout << "Copy assignment operator called" << std::endl;}return *this;}Example(Example &&other) noexcept : value(other.value) {other.value = nullptr;std::cout << "Move constructor called" << std::endl;}Example& operator=(Example &&other) noexcept {if (this != &other) {delete value;value = other.value;other.value = nullptr;std::cout << "Move assignment operator called" << std::endl;}return *this;}~Example() {delete value;std::cout << "Destructor called" << std::endl;}int getValue() const { return *value; }
private:int* value;
};int main() {Example ex1(10);Example ex2 = std::move(ex1);  // 调用移动构造函数Example ex3(20);ex3 = std::move(ex2);          // 调用移动赋值运算符return 0;
}
  1. 练习13.4:编写一个类,阻止其对象的拷贝和赋值。
    • 示例代码
#include <iostream>class NonCopyable {
public:NonCopyable() = default;NonCopyable(const NonCopyable &) = delete;NonCopyable& operator=(const NonCopyable &) = delete;
};int main() {NonCopyable obj1;// NonCopyable obj2 = obj1;  // 错误:拷贝构造函数被删除// NonCopyable obj3;// obj3 = obj1;  // 错误:拷贝赋值运算符被删除return 0;
}

总结与提高

本节总结

  1. 掌握了拷贝控制的基本概念和操作,包括拷贝构造函数、拷贝赋值运算符和析构函数。
  2. 理解了拷贝控制函数在对象复制、赋值和销毁过程中的作用和调用时机。
  3. 学会了在类中正确实现拷贝控制函数,以确保对象在不同生命周期阶段的正确行为。
  4. 理解了“三/五法则”的重要性,学会了在类设计中同时实现必要的拷贝控制函数和移动语义。
  5. 掌握了如何通过delete关键字阻止对象的拷贝和赋值。

提高建议

  1. 多练习拷贝控制函数的实现:通过编写更多涉及拷贝控制的类,熟悉各种管理方法的用法,提高对拷贝控制的理解和实现能力。
  2. 深入理解资源管理的原理:通过阅读文档和相关书籍,深入理解资源管理的实现原理和使用场景,提高编写高效代码的能力。
  3. 优先使用智能指针:在实际项目中,尽量使用智能指针管理动态内存,以减少手动内存管理带来的错误,提高代码的可读性和可维护性。
  4. 优化移动语义:在类设计中,合理运用移动构造函数和移动赋值运算符,提高程序的性能和资源利用效率。

13.2 拷贝控制与资源管理

拷贝控制函数在管理资源时非常重要,特别是当类涉及动态内存分配或其他资源(如文件句柄、网络连接等)时。通过正确实现拷贝控制函数,可以确保资源在对象复制、赋值和销毁过程中得到正确的管理和释放。

13.2.1 拷贝控制与动态内存

在涉及动态内存的类中,拷贝构造函数、拷贝赋值运算符和析构函数的实现需要特别注意,以确保内存安全。

示例代码

#include <iostream>
#include <algorithm> // for std::copyclass DynamicArray {
public:DynamicArray(size_t size) : size(size), data(new int[size]()) {}DynamicArray(const DynamicArray &other) : size(other.size), data(new int[other.size]) {std::copy(other.data, other.data + other.size, data);std::cout << "Copy constructor called" << std::endl;}DynamicArray& operator=(const DynamicArray &other) {if (this != &other) {

相关文章:

《C++ Primer》导学系列:第 13 章 - 拷贝控制

13.1 拷贝、赋值与析构函数 拷贝控制是C++中类设计的重要组成部分,用于管理对象的复制、赋值和销毁过程。理解并正确实现拷贝控制函数(拷贝构造函数、拷贝赋值运算符和析构函数)对于编写健壮和高效的C++程序至关重要。 13.1.1 拷贝构造函数 拷贝构造函数用于创建对象的副…...

c++ 图论2 深度优先算法和广度优先算法

修改一下深度优先算法和广度优先算法&#xff0c;标出每一个节点相对于遍历起始位置的层级&#xff0c;遍历起始起点为第一层&#xff0c;和第一层相连的节点为第二层&#xff0c;以此类推 定义一个新的结构 struct NodeWithLevel {TreeNode* node;int level;NodeWithLevel(T…...

【Qt】初识QtQt Creator

一.简述Qt 1.什么是Qt Qt 是⼀个 跨平台的 C 图形⽤⼾界⾯应⽤程序框架 。它为应⽤程序开发者提供了建⽴艺术级图形界⾯所需的所有功能。它是完全⾯向对象的&#xff0c;很容易扩展。Qt 为开发者提供了⼀种基于组件的开发模式&#xff0c;开发者可以通过简单的拖拽和组合来实现…...

Android 11.0 修改系统显示大小导航栏消失

Android 11.0 修改系统显示大小导航栏消失 1.显示大小设置为大时&#xff0c;导航栏图标不显示。 设置为大&#xff0c;较大&#xff0c;最大时&#xff0c;导航栏图标不显示。 2.开始怀疑是导航栏被隐藏了&#xff0c;各种折腾无效。 3.发现&#xff1a; frameworks/base/pa…...

RocketMQ源码学习笔记:Producer启动流程

这是本人学习的总结&#xff0c;主要学习资料如下 马士兵教育rocketMq官方文档 目录 1、Overview1.1、创建MQClientInstance1.1.1、检查1.1.1、MQClientInstance的ID 1.2、MQClientInstance.start() 1、Overview 这是发送信息的代码样例&#xff0c; DefaultMQProducer produ…...

Node.js 和浏览器环境中都使用 WebSocket

使用WebSocket为什么不适配双端 浏览器环境本身就支持 WebSocket&#xff0c;直接使用 JavaScript 内置的 WebSocket 对象来建立连接。 Node中本身并没有内置 WebSocket 协议的支持&#xff0c;所以需要使用第三方库 ws来实现 WebSocket 功能。 一. 使用跨平台 WebSocket 库 …...

css美化滚动条样式

效果展示 实现 滚动条宽&#xff0c;高度 /* 整体滚动条 */ ::-webkit-scrollbar {width: 10px; }/* 滚动条轨道 */ ::-webkit-scrollbar-track {background-color: #ffffff;border-radius: 6px; }/* 滚动条滑块 */ ::-webkit-scrollbar-thumb {background-color: #888;borde…...

由浅入深,走进深度学习(补充篇:转置卷积和FCN)

本期内容是针对神经网络层结构的一个补充&#xff0c;主要内容是&#xff1a;转置卷积和全连接卷积网络 相关内容&#xff1a; 由浅入深&#xff0c;走进深度学习&#xff08;2&#xff09;_卷积层-CSDN博客 由浅入深&#xff0c;走进深度学习&#xff08;补充篇&#xff1a…...

Linux基础篇——目录结构

基本介绍 Linux的文件系统是采用级层式的树状目录结构&#xff0c;在此结构中的最上层是根目录"/"&#xff0c;然后在根目录下再创建其他的目录 在Linux中&#xff0c;有一句经典的话&#xff1a;在Linux世界里&#xff0c;一切皆文件 Linux中根目录下的目录 具体的…...

星际编码:Swifter.Json,.NET宇宙中的数据处理新星

概述 在数字化的星辰大海中&#xff0c;数据是宇宙的通用语言。在.NET这一广袤的星系中&#xff0c;JSON作为信息交换的媒介&#xff0c;扮演着至关重要的角色。今天&#xff0c;我们要探索的是一颗新星——Swifter.Json&#xff0c;一个功能全面且性能卓越的JSON序列化和反序列…...

python 压缩数据

requests 是 Python 中一个非常流行的 HTTP 库&#xff0c;用于发送各种 HTTP 请求。下面是一个使用 requests 库发送简单 GET 请求和 POST 请求的示例&#xff1a; 首先&#xff0c;确保你已经安装了 requests 库。如果还没有安装&#xff0c;可以使用 pip 进行安装&#xff…...

nacos在k8s上的集群安装实践

目录 概述实践nfs安装使用 k8s持久化nacos安装创建角色部署数据库执行数据库初始化语句部署nacos ingress效果展示问题修复 结束 概述 本文主要对 nacos 在k8s上的集群安装 进行说明与实践。主要版本信息&#xff0c;k8s: 1.27.x&#xff0c;nacos: 2.0.3。运行环境为 centos 7…...

数据结构—判断题

1.数据的逻辑结构说明数据元素之间的顺序关系&#xff0c;它依赖于计算机的存储结构。 答案&#xff1a;错误 2.(neuDS)在顺序表中逻辑上相邻的元素&#xff0c;其对应的物理位置也是相邻的。 答案&#xff1a;正确 3.若一个栈的输入序列为{1, 2, 3, 4, 5}&#xff0c;则不…...

树莓派挂载的移动硬盘badblocks坏道屏蔽,以这个为准

!!!use 这里要设置块大小和磁盘相同 badblocks -b 4096 -s -c 512 -v -o /a/2/bads4.txt /dev/sda5 检测完重新检测跳过之前的记录 badblocks -i /a/2/bads4.txt -b 4096 -s -c 512 -v -o /a/2/bads5.txt /dev/sda5 可以查看磁盘具体block总数和大小 sudo dumpe2fs /dev/sda5 …...

Unity开箱即用的UGUI面板的拖拽移动功能

文章目录 &#x1f449;一、背景&#x1f449;二、效果图&#x1f449;三、原理&#x1f449;四、核心代码&#x1f449;五&#xff0c;总结 &#x1f449;一、背景 之前做PC项目时常常有面板拖拽移动的需求&#xff0c;今天总结封装一下&#xff0c;做成一个随时随地可复用的…...

春秋云境:CVE-2022-25411[漏洞复现]

根据题目提示和CNNVD优先寻找后台管理地址 靶机启动后&#xff0c;使用AWVS进行扫描查看网站结构 在这里可以看到后台管理的登录地址&#xff1a;/admin/&#xff0c;根据题目提示可知是弱口令 尝试admin、123456、admin666、admin123、admin888...等等常见弱口令 正确的账户…...

java基础知识点全集

JAVA的所有知识点 一、基础的数组、数据类型、输入输出二、类与对象1. 三大特征&#xff08;1&#xff09; 封装&#xff08;2&#xff09;继承&#xff08;3&#xff09;多态 2. 类的实例化&#xff08;1&#xff09; 类通过NEW来创建&#xff08;2&#xff09; 类的继承&…...

如何完成域名解析验证

一&#xff1a;什么是DNS解析&#xff1a; DNS解析是互联网上将人类可读的域名&#xff08;如www.example.com&#xff09;转换为计算机可识别的IP地址&#xff08;如192.0.2.1&#xff09;的过程&#xff0c;大致遵循以下步骤&#xff1a; 查询本地缓存&#xff1a;当用户尝…...

2024年6月个人工作生活总结

title: 2024年6月个人工作生活总结 urlname: code-for-2024-06 tags: 代码积累知识总结 categories:我的程序代码 date: 2024-06-30 00:00:00 photos:gallery/tech/c2.jpg 本文为 2024年6月工作生活总结。 研发编码 编码和注释 因某些需要&#xff0c;重拾了2019年的工程代码…...

Json与Java类

简介 JSON&#xff08;JavaScript Object Notation&#xff09;是一种轻量级的数据交换格式&#xff0c;易于人阅读和编写&#xff0c;同时也易于机器解析和生成。JSON数据由键值对构成&#xff0c;并以易于阅读的文本形式展现&#xff0c;支持数组、对象、字符串、数字、布尔值…...

AI Agent与Agentic AI:原理、应用、挑战与未来展望

文章目录 一、引言二、AI Agent与Agentic AI的兴起2.1 技术契机与生态成熟2.2 Agent的定义与特征2.3 Agent的发展历程 三、AI Agent的核心技术栈解密3.1 感知模块代码示例&#xff1a;使用Python和OpenCV进行图像识别 3.2 认知与决策模块代码示例&#xff1a;使用OpenAI GPT-3进…...

【解密LSTM、GRU如何解决传统RNN梯度消失问题】

解密LSTM与GRU&#xff1a;如何让RNN变得更聪明&#xff1f; 在深度学习的世界里&#xff0c;循环神经网络&#xff08;RNN&#xff09;以其卓越的序列数据处理能力广泛应用于自然语言处理、时间序列预测等领域。然而&#xff0c;传统RNN存在的一个严重问题——梯度消失&#…...

在 Nginx Stream 层“改写”MQTT ngx_stream_mqtt_filter_module

1、为什么要修改 CONNECT 报文&#xff1f; 多租户隔离&#xff1a;自动为接入设备追加租户前缀&#xff0c;后端按 ClientID 拆分队列。零代码鉴权&#xff1a;将入站用户名替换为 OAuth Access-Token&#xff0c;后端 Broker 统一校验。灰度发布&#xff1a;根据 IP/地理位写…...

Axios请求超时重发机制

Axios 超时重新请求实现方案 在 Axios 中实现超时重新请求可以通过以下几种方式&#xff1a; 1. 使用拦截器实现自动重试 import axios from axios;// 创建axios实例 const instance axios.create();// 设置超时时间 instance.defaults.timeout 5000;// 最大重试次数 cons…...

Mysql8 忘记密码重置,以及问题解决

1.使用免密登录 找到配置MySQL文件&#xff0c;我的文件路径是/etc/mysql/my.cnf&#xff0c;有的人的是/etc/mysql/mysql.cnf 在里最后加入 skip-grant-tables重启MySQL服务 service mysql restartShutting down MySQL… SUCCESS! Starting MySQL… SUCCESS! 重启成功 2.登…...

提升移动端网页调试效率:WebDebugX 与常见工具组合实践

在日常移动端开发中&#xff0c;网页调试始终是一个高频但又极具挑战的环节。尤其在面对 iOS 与 Android 的混合技术栈、各种设备差异化行为时&#xff0c;开发者迫切需要一套高效、可靠且跨平台的调试方案。过去&#xff0c;我们或多或少使用过 Chrome DevTools、Remote Debug…...

渗透实战PortSwigger靶场:lab13存储型DOM XSS详解

进来是需要留言的&#xff0c;先用做简单的 html 标签测试 发现面的</h1>不见了 数据包中找到了一个loadCommentsWithVulnerableEscapeHtml.js 他是把用户输入的<>进行 html 编码&#xff0c;输入的<>当成字符串处理回显到页面中&#xff0c;看来只是把用户输…...

医疗AI模型可解释性编程研究:基于SHAP、LIME与Anchor

1 医疗树模型与可解释人工智能基础 医疗领域的人工智能应用正迅速从理论研究转向临床实践,在这一过程中,模型可解释性已成为确保AI系统被医疗专业人员接受和信任的关键因素。基于树模型的集成算法(如RandomForest、XGBoost、LightGBM)因其卓越的预测性能和相对良好的解释性…...

Linux基础开发工具——vim工具

文章目录 vim工具什么是vimvim的多模式和使用vim的基础模式vim的三种基础模式三种模式的初步了解 常用模式的详细讲解插入模式命令模式模式转化光标的移动文本的编辑 底行模式替换模式视图模式总结 使用vim的小技巧vim的配置(了解) vim工具 本文章仍然是继续讲解Linux系统下的…...

基于Uniapp的HarmonyOS 5.0体育应用开发攻略

一、技术架构设计 1.混合开发框架选型 &#xff08;1&#xff09;使用Uniapp 3.8版本支持ArkTS编译 &#xff08;2&#xff09;通过uni-harmony插件调用原生能力 &#xff08;3&#xff09;分层架构设计&#xff1a; graph TDA[UI层] -->|Vue语法| B(Uniapp框架)B --&g…...