问题
在某些场景中(例如并查集),我们需要将新建对象中的指针指向对象自己。例如,
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已经开辟的空间使用完毕后,再加入新的元素需要重新开辟空间,并且将整个"对象数组"复制过去。此时依然会发生指针指向的位置与实际对象位置不匹配的错误。