C++中使用vector保存新建对象中自指指针的问题

问题

在某些场景中(例如并查集),我们需要将新建对象中的指针指向对象自己。例如,

cpp 复制代码
struct factor {
	int data;
	factor* next;

	factor(int i) : data(i), next(this){}
};

这样的结构体当然没有问题,如果我们想以类似链表的形式将元素串联起来,并且找到链表的头部(也就是next指针指向自己的元素)。

其中num1->num2,num2->num3,num3->num4,num4->num4。然后打印num4的值,得到结果4。到目前为止,进行的很顺利,没有意外发生。

如果我们不想手动挨个创建factor对象,而是将他们保存在STL容器中。可以这样做

cpp 复制代码
vector<factor> factor_array;
vector<int> nums = { 1,2,3,4,5,6 };

for (int i = 0; i < nums.size(); i++) {
	factor_array.emplace_back(nums[i]);
}

factor_array[0].next = &factor_array[2];
factor_array[2].next = &factor_array[1];
factor_array[1].next = &factor_array[3];

factor* p = &factor_array[0];
while (p->next != p) {
	p = p->next;
}

cout << p->data;

编译,运行!

哦嚯,出问题了。

首先排除是我们的factor类写的不对,因为我们已经验证过,手动创建每个对象时的逻辑是正确的。

那么是不是emplace_back的问题呢?将emplace_back改成push_back试一试。

成功得到结果。但是这个结果有点不对劲,0指向2,2指向1,1指向3,3初始化指向自己,为什么最后结果是6呢?

解决问题

这样,我们将每一个创建后的对象结果打印出来。

cpp 复制代码
	vector<factor> factor_array;
	vector<int> nums = { 1,2,3,4,5,6 };

	for (int i = 0; i < nums.size(); i++) {
		factor_array.push_back(nums[i]);
		for (int j = 0; j < factor_array.size(); j++) {
			cout << factor_array[j].data << "'s next points at " << factor_array[j].next << "  " << factor_array[j].next->data << endl;
		}
		cout << endl;
	}

	factor_array[0].next = &factor_array[2];
	factor_array[2].next = &factor_array[1];
	factor_array[1].next = &factor_array[3];

	factor* p = &factor_array[0];
	while (p->next != p) {
		p = p->next;
	}

	cout << p->data;

这样,我们发现了问题的所在。每次创建对象时,this指向的位置是相同的!这与push_back和emplace_back的实现原理有关,我们后面展开说。

我们进一步获取更详细的信息,

cpp 复制代码
struct factor {
	int data;
	factor* next;

	factor(int i) : data(i), next(this) { cout << data << " created at " << this << endl; }
};

在factor的构造函数中打印出当前对象是在哪块内存中创建的。

cpp 复制代码
	vector<factor> factor_array;
	vector<int> nums = { 1,2,3,4,5,6 };

	for (int i = 0; i < nums.size(); i++) {
		factor_array.push_back(nums[i]);
		cout << factor_array[i].data << " at " << &factor_array[i] << " next is  " << factor_array[i].next << endl;
	}
	cout << endl;

	for (int j = 0; j < factor_array.size(); j++) {
		cout << factor_array[j].data << " at " << &factor_array[j] << " next is  " << factor_array[j].next << endl;
	}
	cout << endl;

	factor_array[0].next = &factor_array[2];
	factor_array[2].next = &factor_array[1];
	factor_array[1].next = &factor_array[3];

	factor* p = &factor_array[0];
	while (p->next != p) {
		p = p->next;
	}

	cout << p->data;

比较一下创建前后对象在内存中的位置。我们得到结果为

现在的情况我们分析一下,在vector在创建我们的对象时,所有对象的创建都是在一块空间内完成的。根据我们factor的构造函数来看,next指针理所当然的指向了创建对象的那块空间。后续过程vector将创建好的对象复制到真正保存数据的内存中,然而,所有对象的next指针依然指向创建时的内存地址。该地址在vector创建对象结束后被释放,但是并没有被其他程序改写,所以factor* p 依然能从中读取到最后创建的对象的信息,也就是6。

所以,如果想让vector中保存的对象中的自指指针指向自己。需要在vector创建后,遍历一次每个对象,使其指针自指。

使用vector保存带有指向自身或者指向其他保存在同一容器中的其他对象的指针的对象时,在整个vector初始化后,不要再向其中添加其他新的元素。

因为在vector已经开辟的空间使用完毕后,再加入新的元素需要重新开辟空间,并且将整个"对象数组"复制过去。此时依然会发生指针指向的位置与实际对象位置不匹配的错误。

相关推荐
papership几秒前
【入门级-算法-6、排序算法:选择排序】
数据结构·算法·排序算法
RickyWasYoung4 分钟前
【代码】matlab-遗传算法工具箱
开发语言·matlab
郝学胜-神的一滴6 分钟前
基于OpenGL封装摄像机类:视图矩阵与透视矩阵的实现
c++·qt·线性代数·矩阵·游戏引擎·图形渲染
啊?啊?27 分钟前
14 C++ STL 容器实战:stack/list 模拟实现指南 + priority_queue 用法及避坑技巧
c++·
汉克老师35 分钟前
第十四届蓝桥杯青少组C++选拔赛[2023.2.12]第二部分编程题(4、最大空白区)
c++·算法·蓝桥杯·蓝桥杯c++·c++蓝桥杯
共享家952741 分钟前
优先搜索(DFS)实战
算法·leetcode·深度优先
一只懒洋洋1 小时前
中值滤波、方框滤波、高斯滤波、均值滤波、膨胀、腐蚀、开运算、闭运算
算法·均值算法
shellvon1 小时前
你怎么被识别的?从TLS到Canvas的设备追踪术
后端·算法
薛定谔的算法1 小时前
JavaScript栈的实现与应用:从基础到实战
前端·javascript·算法
土了个豆子的1 小时前
03.缓存池
开发语言·前端·缓存·visualstudio·c#