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

C++_Bug:现代写法拷贝构造中 swap 写法之小坑

文章又名:深入剖析HashTable赋值拷贝中std::swap的那些坑

案发背景

bear实现了链地址法的HashTable,最后在补充析构函数、赋值拷贝和拷贝构造这里。博主果断采用swap现代写法——

namespace hash_bucket
{
template<class K,class V,class HashFunc=hashfunc<K>>class HashTable{public:HashTable():_table(11),_n(0){}~HashTable(){for (int i = 0; i < _table.size(); i++){HashData<K, V>* head = _table[i];while (head){HashData<K, V>* next = head->_next;delete head;head = next;}_table[i] = nullptr;}_n = 0;}HashTable(const HashTable& ht):_table(11), _n(0){// 拷贝for (int i = 0; i < ht._table.size(); i++){HashData<K, V>* head = ht._table[i];while (head){HashData<K, V>* next = head->_next;Insert(head->_kv);head = next;}}_n = ht._n;}HashTable& operator=(HashTable ht){std::swap(ht,*this);return *this;}
//... 以下为增删查改具体代码 (忽略)
}

一旦实现了拷贝构造,我就可以在赋值拷贝里使用转移大法

HashTable& operator=(HashTable ht){std::swap(ht,*this);return *this;}

案发现场

测试代码:

/** 测试4:析构、赋值重载和拷贝构造* 
*/
namespace hash_bucket
{void test4(){HashTable<int, int> ht;int a[] = { 19,30,5,36,13,20,21,12 };for (auto e : a){ht.Insert({ e,e });}HashTable<int, int> ht3;int a2[] = { 18, 25, 36, 49, 5, 13, 28, 7, 31, 17, 42, 55, 11, 22 };for (auto e : a2){ht3.Insert({ e,e });}HashTable<int, int> ht2 = ht;//拷贝构造ht2.Print(); cout << "----------ht2--------------" << endl;ht3.Print(); cout << "----------ht3--------------" << endl;ht3 = ht2;// 赋值拷贝ht3.Print(); cout << "----------ht3--------------" << endl;}
}
int main()
{hash_bucket::test4();return 0;
}

运行截图:

图1 代码崩溃

真相是什么

很明显,赋值拷贝的代码出了问题。

可是调用了库里的swap怎么还能出错呢?

分析

std::swap 对自定义类型的默认实现本质是 “三次赋值”:

template<class T>
void swap(T& a, T& b) {T temp(a);  // 1. 用a拷贝构造tempa = b;      // 2. 用b赋值给ab = temp;   // 3. 用temp赋值给b
}

当你在赋值运算符中调用 std::swap(ht, *this) 时,相当于:

HashTable& operator=(HashTable ht) {// 此时ht是实参的拷贝(已通过你的拷贝构造完成深拷贝)HashTable temp(ht);   // 用ht拷贝构造temp(再次深拷贝)ht = *this;           // 用*this赋值给ht(调用当前operator=,导致递归!)*this = temp;         // 用temp赋值给*thisreturn *this;
}

是的,这里会导致递归。而且这里还多进行了一次深拷贝:
多做一次深拷贝(temp 的创建),我们的需求只是 “交换资源”,无需额外拷贝。

解决

HashTable& operator=(HashTable ht){std::swap(ht._table,_table);std::swap(ht._n, _n);return *this;}

运行截图:

图2 代码运行成功

为什么显式交换成员变量没问题?

HashTable& operator=(HashTable ht) {std::swap(ht._table, _table);  // 直接交换资源容器(指针/vector)std::swap(ht._n, _n);          // 交换元素个数return *this;
}
  • 没有触发 std::swap 对整个对象的默认三次赋值,因此不会递归调用 operator=
  • 仅交换资源的 “所有权”(_table 存储的指针 / 链表节点的归属权从 ht 转移到 *this),无需额外深拷贝,效率更高。
  • 依赖 ht 的析构函数释放 *this 原来的旧资源(因为 ht 是局部变量,离开作用域时会自动析构),逻辑清晰且安全。

提问:std::swap(ht._table,_table);在swap里,_table和ht._table交换过程中还不是会拷贝出来一个temp;这和我原来的写法有什么区别

  • 明确 std::swap(ht._table, _table) 中 temp 拷贝的是什么
std::swap(ht._table, _table); 
// 针对vector的swap实现(简化版)
void swap(vector<HashData*>& a, vector<HashData*>& b) {vector<HashData*> temp(a);  // 用a拷贝构造temp(拷贝的是vector容器本身)a = b;                      // 把b的vector内容赋给ab = temp;                   // 把temp的内容赋给b
}

这里的 temp 拷贝的是 vector 容器本身(容器内的指针会被复制,但指针指向的链表节点内存不会被复制)。

  • 原来的 std::swap(ht, *this) 中 temp 拷贝的是什么
void swap(HashTable& a, HashTable& b) {HashTable temp(a);  // 用a拷贝构造temp(调用深拷贝构造函数)a = b;              // 调用operator=,导致递归b = temp;           // 再次调用operator=
}

这里的 temp 拷贝的是 整个 HashTable 对象,包括:

  • 调用深拷贝构造函数,为 temp 重新分配所有链表节点内存(完整复制 _table 中的每个节点,成本极高);
  • 同时复制 _n 等其他成员。
总结:swap(成员) 是 “轻量转移”,swap(对象) 是 “重量级灾难”
  • std::swap(ht._table, _table) 中,temp 拷贝的是 vector 容器(轻量操作,不复制节点内存),且不涉及 operator= 调用,因此高效且安全;
  • 原来的 std::swap(ht, *this) 中,temp 拷贝的是整个对象(触发深拷贝,复制所有节点),且强制调用 operator= 导致递归,因此既低效又危险。

也算是查漏补缺,swap写法这个问题我一直没在意,今天就出现了。

不管了,感谢遇见(冷脸)

http://www.dtcms.com/a/617822.html

相关文章:

  • 通关upload-labs(14-21)加分析源码
  • 【目标检测】YOLOv10n-ADown弹孔检测与识别系统
  • 扬中网站推广导流网盘怎么做电影网站
  • 【C++】:priority_queue的理解,使用和模拟实现
  • 深圳南山网站建设公司做网络推广需要多少钱
  • Rust中的集合Collection
  • Git 配置实践
  • 学习笔记十:多分类学习
  • 【实战案例】基于dino-4scale_r50_8xb2-36e_coco的棉田叶片病害识别与分类项目详解
  • opencv学习笔记9:基于CNN的mnist分类任务
  • 分布式系统中MPSC队列的内存回收策略适配避坑
  • Git笔记---分支相关操作
  • 基于YOLOv8的汽车目标检测系统实现与优化_含多种车型识别与自动驾驶应用场景
  • 广东省建设工程协会网站如何查看一个网站是不是用h5做的
  • 开发STM32日记1:安装软件、配置软件(芯片为STM32F103C8T6 )
  • 【Git】处理报错原因
  • 基于Bboss框架的ElasticSearch并发更新版本冲突问题解决
  • Highcharts常见问题解析(5):如何将多个图表导出到同一张图片或 PDF?
  • 什么是中间件?必须要有中间件吗?有哪些国产中间件厂商?
  • 第七章深度解析:从零构建智能体框架——模块化设计与全流程落地指南
  • 机器视觉3D无序抓取如何确保抓取精度,需要从以下五个核心方面入手,形成一个闭环的控制系统
  • Git Bisect - Git Commit 故障排查利器使用详解
  • 青岛科技街网站建设不懂外贸做外贸网站好做吗
  • 2511C++,CTAD简化回调
  • 【ros2】ROS2 C++参数设置指南(含跨节点修改方法)
  • STM32通信接口----USART
  • 解决Web游戏Canvas内容在服务器部署时的显示问题
  • 我爱学算法之—— 哈希
  • Linux字符设备驱动模型
  • C++ List 容器详解:迭代器失效、排序与高效操作