1. 程式人生 > >絕對不要在解構函式中釋放單例-----這個至少3000元的bug讓人蛋疼兩三天

絕對不要在解構函式中釋放單例-----這個至少3000元的bug讓人蛋疼兩三天

        某系統在某特殊情況下, 會出現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確實讓人蛋疼兩三天。 這個程式碼是誰寫的啊驚訝是不是應該在明天端午節請我吃一頓大餐呢大笑我再次大聲疾呼, 軟體質量不是一句廢話。 

       要多反思, 多總結, 總會慢慢進步。本文先到此為止。

相關推薦

絕對不要函式釋放-----這個至少3000bug

        某系統在某特殊情況下, 會出現bug, 經我非常保守地估計, 這個bug的定位修改費用至少3000元, 這還不包括其他的費用。 脫離具體場景, 我來抽象出一個簡單的模型, 示例程式碼如下: #include <iostream> using n

不要函式丟擲異常

轉載 : http://www.cnblogs.com/hbt19860104/archive/2012/10/22/2734006.html (很好的博文,贊!!!。解惑瞭如何處理析構函數出現異常現象,增加對解構函式的工作機制和作用域的相關理解。) 不要在解構函式中丟擲異常 1: 可以

在建構函式/函式呼叫虛擬函式

先看一段在建構函式中直接呼叫虛擬函式的程式碼: 1 #include <iostream> 2 3 class Base 4 { 5 public: 6 Base() { Foo(); } ///< 列印 1 7 8

[c/c++]建構函式函式可不可以丟擲異常

usingnamespace std;class A...{public:    A()    ...{        cout <<"construction fun"<< endl;        throw1;    }    ~A()    

C++ 建構函式函式能否呼叫虛擬函式

牛客網 ------------------- ------------------- ------------------- 設計模式 ------------------- -------------------

建構函式函式不呼叫虛擬函式

本文參考《effective C++》第九條款 在C++中,提倡不能在建構函式和解構函式中呼叫虛擬函式。 這是為什麼呢? 首先,我們先回顧一下C++虛擬函式的作用。 虛擬函式的引入是c++執行時多型的體現,通過呼叫虛擬函式可以在執行程式時實現動態繫結,體現

C++ 構造/函式的異常處理

C++ 為什麼會引入(需要)異常? The C++ Programming Language: 一個庫的作者可以檢測出發生了執行時錯誤,但一般不知道怎樣去處理它們(因為和使用者具體的應用有關);另一方面,庫的使用者知道怎樣處理這些錯誤,但卻無法檢查它們何時發生(如果能

VS2013 小黑框不顯示函式的輸出

請按任意鍵繼續...是呼叫system("pause"); 的結果,但解構函式的呼叫要在return之後。解構函式的輸出看不到。 可在解構函式實現的定義檔案中新增 標頭檔案:#include <fstream> 語句:fstream fout("destru

立此存照26[C++]為什麼VS2013不能顯示函式的輸出語句

#include <iostream> using namespace std; class A { public: A() { cout << "A()" << endl; } ~A() { cout <<

為什麼在函式不應該丟擲異常?

1. 丟擲異常 1.1 丟擲異常(也稱為拋棄異常)即檢測是否產生異常,在C++中,其採用throw語句來實現,如果檢測到產生異常,則丟擲異常。       該語句的格式為: throw 表示式; 如果在try語句塊的程式段中(包括在其中呼叫的函式)發現了異常,且拋棄

java的delete

專案中要刪除資料夾, 只有一層, 下面有zip包, jpg圖片, xml檔案, 但是在刪除時, 有一部分檔案卻刪不掉, delete的結果是false:public void deleteDir(File file) { if (file.exists()) {

設計模式模式的程式碼為什麼函式會多次被呼叫,而建構函式只調用一次

單例模式 package com.seven.exercise.testEception; /**  * 單例模式,餓漢式  * @author Seven  *  */ public class SingleDemoHunger {   &nb

C++ 模式 釋放資源 函式的應用

面試的時候被問到單例模式怎麼釋放資源,當時答的不太好。在網上查了下,找到一篇講解很精彩的部落格,轉載一下。 本文轉自:http://blog.csdn.net/realxie/article/details/7090493 單例模式也稱為單件模式、單子模式,可能是使用最

函式 和 建構函式最好不要呼叫虛擬函式

參考Effective c++ 條款7 和調款9 條款7: 多型性質的基類虛解構函式的重要性! 1、帶多型性質的 base classes應該宣告一個virtual 解構函式, 如果class帶有任何virtual函式,它就應該擁有一個virtual解構函

Python3函式

解構函式                    解構函式:__del__(self)                

C++基類的函式為什麼要用virtual虛函式【轉】

(轉自:https://blog.csdn.net/iicy266/article/details/11906457) 知識背景          要弄明白這個問題,首先要了解下C++中的動態繫結。&n

C++抽象類以及虛/純虛、函式的區別與介紹

一、虛擬函式 在某基類中宣告為 virtual 並在一個或多個派生類中被重新定義的成員函式,用法格式為:virtual+函式返回型別+ 函式名(引數表) {函式體};實現多型性,通過指向派生類的基類指標或引用,訪問派生類中同名覆蓋成員函式。 二、純虛擬函式 純虛擬函式是一種

C++為什麼要將函式定義成虛擬函式

      派生類的成員由兩部分組成,一部分是從基類那裡繼承而來,一部分是自己定義的。那麼在例項化物件的時候,首先利用基類建構函式去初始化從基類繼承而來的成員,再用派生類建構函式初始化自己定義的部分。 同時,不止建構函式派生類只負責自己的那部分,解構函式也是,所以派生

C++函式和delete的關係

https://blog.csdn.net/wk_bjut_edu_cn/article/details/79149540 解構函式 1.建構函式是初始化物件的非static資料成員,在函式體中還可以另外做一些事情;解構函式則是釋放物件使用的資源,並銷

【C++】c++的六個預設函式——函式

解構函式(不能過載,沒有引數,一個類只能有一個解構函式。如果沒定義,編譯器會自動生成一個) 解構函式:建立物件時系統會自動呼叫建構函式進行初始化工作,同樣,銷燬物件時系統也會自動呼叫一個函式來進行清理工作。 解構函式(Destructor)也是一種特殊的成員函式,沒有返回值,不需要