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

相关推荐
runing_an_min3 分钟前
windows运行ffmpeg的脚本报错:av_ts2str、av_ts2timestr、av_err2str => E0029 C4576
c++·windows·ffmpeg·e0029
AiFlutter11 分钟前
Java对接GraphQL
java·开发语言·graphql
无敌岩雀13 分钟前
C++设计模式结构型模式———桥接模式
c++·设计模式·桥接模式
朱玥玥要每天学习24 分钟前
交换排序与快速排序
数据结构·算法·排序算法
非概念28 分钟前
常见的排序算法(一)
数据结构·算法·排序算法
Ning_.31 分钟前
力扣第39题:组合总和(C语言解法)
c语言·算法·leetcode
lqqjuly38 分钟前
深度学习基础知识-Batch Normalization(BN)超详细解析
人工智能·深度学习·算法
麻由由哒哟1 小时前
CSP-J2023T4 旅游巴士(同余最短路)
c++·算法
trueEve1 小时前
SQL,力扣题目262,行程和用户
算法·leetcode·职场和发展
点点滴滴的记录1 小时前
56. 数组中只出现一次的数字
java·数据结构·算法