迁移通知

本站内容正在逐步向 https://www.weiran.ink 迁移,更新内容请到新站查找。


std::vector/指针成员 联用时的大坑

下面这段代码有何问题?

#include <vector>
 
class A {
public:
	A() {
		p = new int;
	}
	~A() {
		delete p;
	};
 
	int * p;
};
 
int main()
{
	std::vector<A> vec;
	for (int i=0; i<5; ++i)
	{
		vec.push_back(A());
	}
	return 0;
}

看起来好像挺正常的。虽然包括了一个指针,但有newdelete一一对应,浓眉大眼的,应该没有问题。

然而,事实是运行起来马上抛异常,诊断发现异常出在delete,显然是试图释放一个不存在的对象。

补充一个小知识:C++标准规定`delete`应该自己检查指针是否为`null_ptr`,所以任何情况下都不用手动检查`null_ptr`问题,不会在这上面出错的。

但是A中唯一的构造函数确定new了一个int出来,p为何会指向一个不存在的对象?

断点半天看不出来问题,于是上log:

class A {
public:
	A() {
		std::cout << "construct A" << std::endl;
		p = new int;
	}
	~A() {
		std::cout << "destroy A" << std::endl;
 
		//delete p;
		//此bug的另一大坑在于,即使打了log,由于程序很快就会出错,log收集到的信息并不多,所以不容易看出来原因。
		//所以诊断代码中需要先屏蔽掉指针操作
	};
 
	int * p;
};
 
int main()
{
	std::vector<A> vec;
	for (int i=0; i<5; ++i)
	{
		std::cout << "i = " << i << std::endl;
		vec.push_back(A());
	}
	std::cout << "cycle end" << std::endl;
	return 0;
}

运行出来的结果很有意思:

construct A
destroy A
i = 1
construct A
destroy A
destroy A
i = 2
construct A
destroy A
destroy A
destroy A
i = 3
construct A
destroy A
destroy A
destroy A
destroy A
i = 4
construct A
destroy A
destroy A
destroy A
destroy A
destroy A
cycle end
destroy A
destroy A
destroy A
destroy A
destroy A

每次循环中都正常新建了一个A。然而,第二次循环中就出现了1次“不应有”的析构,第三次循环中出现了2次,第四次则析构了3个A……

显然,就是这个情况导致了多次析构,而从第三轮循环开始,如果没有屏蔽掉delelte p,程序就会死于尝试析构第二轮循环中已经被析构的那个指针,不信可以输出一下指针地址看看。

继续分析可以知道,这些被析构的对象,显然不是由我们手写的构造函数搞出来的,否则不会没有log。那么只剩下一种途径,就是编译器隐式添加的那个默认复制函数A(const &A)。验证很简单,这里不搞了。

整件事情至此比较明了:每次循环中,整个vector中的所有原有的元素都被默认复制函数浅拷贝了一次,然后原有的对象被析构。再下一次循环中,这些被复制出来的对象又一次被析构,但上次复制出来的指针现在成了野指针,既不合法又不是null_ptr,被delete傻乎乎的拿去用,显然会出错。

为什么会搞出这些反复的复制/析构?也不难猜测:当程序试图增大vector的时候

  • 最后更改: 2025/05/10 05:59