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

相关推荐
Code哈哈笑几秒前
【Java 学习】深度剖析Java多态:从向上转型到向下转型,解锁动态绑定的奥秘,让代码更优雅灵活
java·开发语言·学习
程序猿进阶4 分钟前
深入解析 Spring WebFlux:原理与应用
java·开发语言·后端·spring·面试·架构·springboot
qq_433618446 分钟前
shell 编程(二)
开发语言·bash·shell
闻缺陷则喜何志丹9 分钟前
【C++动态规划 图论】3243. 新增道路查询后的最短距离 I|1567
c++·算法·动态规划·力扣·图论·最短路·路径
charlie11451419120 分钟前
C++ STL CookBook
开发语言·c++·stl·c++20
袁袁袁袁满20 分钟前
100天精通Python(爬虫篇)——第113天:‌爬虫基础模块之urllib详细教程大全
开发语言·爬虫·python·网络爬虫·爬虫实战·urllib·urllib模块教程
ELI_He99927 分钟前
PHP中替换某个包或某个类
开发语言·php
Lenyiin27 分钟前
01.02、判定是否互为字符重排
算法·leetcode
小林熬夜学编程31 分钟前
【Linux网络编程】第十四弹---构建功能丰富的HTTP服务器:从状态码处理到服务函数扩展
linux·运维·服务器·c语言·网络·c++·http
m0_7482361134 分钟前
Calcite Web 项目常见问题解决方案
开发语言·前端·rust