绝对不要在析构函数中释放单例-----这个至少3000元的bug让人蛋疼两三天

972 篇文章 329 订阅
309 篇文章 11 订阅

        某系统在某特殊情况下, 会出现bug, 经我非常保守地估计, 这个bug的定位修改费用至少3000元, 这还不包括其他的费用。 脱离具体场景, 我来抽象出一个简单的模型, 示例代码如下:

 

#include <iostream>
using namespace std;

class A
{
private:
	static A *m_p;

public:
	static A *getSingleTon()
	{
		if(NULL == m_p)
		{
			m_p = new A();
		}

		return m_p;
	}


	~A()
	{
		if(NULL != m_p)
		{
			delete m_p;
			m_p = NULL;
		}
	}
};

A* A::m_p = NULL;


int main()
{
	return 0;
}

       大家可以看看上述程序有什么问题。

 

 

 

       如果没有看出问题, 那你再运行一下如下程序:

 

#include <iostream>
using namespace std;

class A
{
private:
	static A *m_p;

public:
	static A *getSingleTon()
	{
		if(NULL == m_p)
		{
			m_p = new A();
		}

		return m_p;
	}


	~A()
	{
		if(NULL != m_p)
		{
			cout << "xxx" << endl;

			delete m_p; // 递归调用析构
			m_p = NULL;

			cout << "yyy" << endl; // 永远也不会执行
		}
	}
};

A* A::m_p = NULL;


int main()
{
	A *p = A::getSingleTon();
	delete p;

	return 0;
}

        运行发现, 析构函数被多次调用了, 为什么呢?当类的使用者调用delete p;的时候, 实际上就是调用析构函数来释放单例, 但是, 现在类的提供者在析构函数中又delete这个单例, 显然又会调用析构函数, 所以形成了递归调用析构函数, 系统不异常才怪呢。

 

        我们来反思一下, 为什么会出上述问题呢?肯定是写SingleTon的人牢牢记住了: 要在析构函数中释放资源。  但是, 他不明白, 单例应该由类的使用者来释放, 而不是类的提供者。 不要把角色搞错了。 

        千万不要说, 为什么出这么低级的问题! 其实, 这个问题不低级, 是个比较隐蔽的错误。 而且, 当代码多了(比如100w行), 离职的人多了, 经几次交接后, 那代码就可想而知了哭

 

         下面, 我们继续看看:

 

#include <iostream>
using namespace std;

class A
{
private:
	static A *m_p;
	
	int x;

	A()
	{
		x = 1;
	}

public:
	static A *getSingleTon()
	{
		if(NULL == m_p)
		{
			m_p = new A();
		}

		return m_p;
	}


	~A()
	{
		if(NULL != m_p)
		{
			cout << "xxx" << x << endl; // 永远是xxx1

			delete m_p; // 递归调用析构
			m_p = NULL;

			cout << "yyy" << x << endl; // 永远也不会执行, 单例也不会被释放
		}
	}
};

A* A::m_p = NULL;


int main()
{
	A *p = A::getSingleTon();
	delete p;

	return 0;
}

       从结果看, x的值一直是1, 所以单例根本就没有析构掉, 也就是说, 没有执行析构函数右边的花括号处, 单例就不会被释放。 

 

 

 

       实际上, 要快速定位到析构函数的问题, 还是很不容易的, 那么多代码, 进程死掉, 咋快速定位?尤其是, 如果析构函数中没有日志打印, 根本就很难知道析构函数被多次执行了。 所以, 关于日志, 我强烈建议:

       1. 所有的构造函数和析构函数都必须有日志打印。

       2. 不被频繁调用的函数中, 必须有日志(很多人只喜欢在某些异常分支打日志, 甚至连异常分支都不打印日志, 确实太流氓了)。

 

       好吧, 一个小小的bug确实让人蛋疼两三天。 这个代码是谁写的啊惊讶是不是应该在明天端午节请我吃一顿大餐呢大笑我再次大声疾呼, 软件质量不是一句废话。 

 

       要多反思, 多总结, 总会慢慢进步。本文先到此为止。

 

        
 

 

 

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值