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

相关推荐
小王努力学编程9 分钟前
高并发内存池(三):TLS无锁访问以及Central Cache结构设计
jvm·数据结构·c++·学习
q5673152319 分钟前
Go语言多线程爬虫与代理IP反爬
开发语言·爬虫·tcp/ip·golang
Chandler2421 分钟前
Go语言即时通讯系统 开发日志day1
开发语言·后端·golang
强化学习与机器人控制仿真1 小时前
openpi 入门教程
开发语言·人工智能·python·深度学习·神经网络·机器人·自动驾驶
不是吧这都有重名1 小时前
[论文阅读]Deeply-Supervised Nets
论文阅读·人工智能·算法·大语言模型
homelook1 小时前
matlab simulink双边反激式变压器锂离子电池均衡系统,双目标均衡策略,仿真模型,提高均衡速度38%
算法
明月看潮生1 小时前
青少年编程与数学 02-019 Rust 编程基础 08课题、字面量、运算符和表达式
开发语言·青少年编程·rust·编程与数学
什码情况2 小时前
星际篮球争霸赛/MVP争夺战 - 华为OD机试真题(A卷、Java题解)
java·数据结构·算法·华为od·面试·机试
天天打码2 小时前
Rspack:字节跳动自研 Web 构建工具-基于 Rust打造高性能前端工具链
开发语言·前端·javascript·rust·开源
Petrichorzncu2 小时前
Lua再学习
开发语言·学习·lua