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

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

案发背景

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

cpp 复制代码
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;
		}
//... 以下为增删查改具体代码 (忽略)
}

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

cpp 复制代码
HashTable& operator=(HashTable ht)
		{
			std::swap(ht,*this);
			return *this;
		}

案发现场

测试代码:

cpp 复制代码
/*
	* 测试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;
	}
}
cpp 复制代码
int main()
{
	hash_bucket::test4();
	return 0;
}

运行截图:
图1 代码崩溃

真相是什么

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

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

分析

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

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

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

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

是的,这里会导致递归。而且这里还多进行了一次深拷贝:

多做一次深拷贝(temp 的创建),我们的需求只是 "交换资源",无需额外拷贝。

解决

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

运行截图:
图2 代码运行成功

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

cpp 复制代码
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 拷贝的是什么
cpp 复制代码
std::swap(ht._table, _table); 
cpp 复制代码
// 针对vector的swap实现(简化版)
void swap(vector<HashData*>& a, vector<HashData*>& b) {
    vector<HashData*> temp(a);  // 用a拷贝构造temp(拷贝的是vector容器本身)
    a = b;                      // 把b的vector内容赋给a
    b = temp;                   // 把temp的内容赋给b
}

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

原来的 std::swap(ht, *this)temp 拷贝的是什么
cpp 复制代码
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写法这个问题我一直没在意,今天就出现了。

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

相关推荐
2401_841495642 分钟前
【LeetCode刷题】寻找重复数
数据结构·python·算法·leetcode·链表·数组·重复数
Joe_Blue_0236 分钟前
Matlab入门案例介绍—常用的运算符及优先级
开发语言·数据结构·matlab·matlab基础入门案例介绍
C雨后彩虹41 分钟前
二维伞的雨滴效应
java·数据结构·算法·华为·面试
一路往蓝-Anbo43 分钟前
C语言从句柄到对象 (八) —— 当对象会说话:观察者模式与事件链表
c语言·开发语言·数据结构·stm32·单片机·观察者模式·链表
郭涤生43 分钟前
fmtlib/fmt仓库熟悉
c++
Stanford_11061 小时前
【2026新年启程】学习之路,探索之路,技术之路,成长之路……都与你同行!!!
前端·c++·学习·微信小程序·排序算法·微信开放平台
youngee111 小时前
hot100-60子集
数据结构·算法
郝学胜-神的一滴1 小时前
Linux线程属性设置分离技术详解
linux·服务器·数据结构·c++·程序人生·算法
w-w0w-w2 小时前
C++构造函数初始化列表全解析
c++
梵尔纳多2 小时前
初识 OpenGL
c++·图形渲染