2008-11-07 21 views
27

不應該使用實例/引用計數器的單例對象是否被認爲是C++中的內存泄漏?Singleton Destructors

沒有,對於單一實例的顯式刪除調用當計數爲零的計數器,如何對象被刪除?應用程序終止時是否由操作系統清理?如果那個辛格爾頓在堆上分配了內存呢?

概括地說,我得叫Singelton的析構函數或者我可以依靠它得到清理,當應用程序終止?

回答

14

你可以依靠它被操作系統清理。

這就是說,如果你在使用終結器而不是析構函數的垃圾收集語言中,你可能希望有一個正常的關閉過程,可以直接乾淨地關閉你的單例,以便在使用系統資源時可以釋放任何關鍵資源不會僅僅通過結束應用程序來正確清理。 這是因爲終結者在大多數語言中都以「盡力而爲」的方式運行。另一方面,需要這種可靠性的資源非常少。文件句柄,內存等都乾淨地回到操作系統。

如果您正在使用像C++這樣的語言使用真正的析構函數而不是終結器來分配(即使用三重檢查鎖定語言)的單例,那麼您不能依賴在程序關閉期間調用它的析構函數。如果您使用的是單個靜態實例,則析構函數將在主要完成某個點後運行。

無論如何,當進程結束時,所有內存都會返回到操作系統。

0

任何堆內存的進程分配並沒有被釋放(刪除)將由OS回收。如果您使用的是使用靜態變量的單例的最常見實現,那麼在您的應用程序終止時也可以清理這個單例。

*這並不意味着你應該去各地的新-ING指針,從不清洗起來雖然。

1

一個singleton將是你的對象的一個​​實例。這就是爲什麼它不需要計數器。如果它在你的應用程序的長度上存在,那麼默認的析構函數將會很好。無論如何,內存將在操作系統結束時回收。

3

當進程終止時,操作系統會自動清除除共享內存以外的任何類型的分配。因此,你不應該顯式地調用單例析構函數。換句話說無泄漏 ...

而且像邁爾斯辛格爾頓一個典型的單身實現不僅在第一次調用初始化期間線程安全的,但也保證優美終止應用程序退出時(析構函數調用)。

無論如何,如果該應用程序發送一個UNIX信號(即:SIGTERMSIGHUP)的默認行爲是終止進程,而不調用靜態分配的對象(單身)的析構函數。爲了克服這些信號的問題,可以設置處理程序調用出口,或者處理出口處理程序 - signal(SIGTERM,exit);

+0

如果您依賴運行時庫在主返回後銷燬您的靜態對象,並且希望它可以在(Windows)DLL中使用代碼,那麼您在DllMain期間正在運行代碼,並且您可能會執行大多數操作喜歡做的事情是不安全的。 – 2010-08-13 20:32:00

1

取決於您對泄漏的定義。沒有限制的記憶增加是我書中的一個漏洞,單身並不是沒有約束的。如果您不提供參考計數,則有意讓實例保持活動狀態。不是意外,不是泄漏。

你的單包裝的析構函數應該刪除的情況下,它不是自動的。如果它只分配內存和操作系統資源,那沒有意義。

11

您應該明確清理所有對象。永遠不要依靠操作系統來爲你清理。

在哪裏我通常使用一個單獨的封裝的類似文件,硬件資源,控制等。如果我不正確地清理連接 - 我可以很容易泄漏系統資源。應用程序下次運行時,如果資源仍被上一個操作鎖定,則可能會失敗。另一個問題可能是任何終結 - 如寫入緩衝區到磁盤 - 可能不會發生,如果它仍然存在於單身實例所擁有的緩衝區中。

這是不是一個內存泄漏發出─問題是更,你可能會泄漏等等的其它資源比內存可能不那麼容易恢復。

+0

+1的確如此,但是有些單例確保了應用程序退出時析構函數的調用。 – 2008-11-08 11:33:23

+1

如果您依賴運行時庫在主返回後銷燬您的靜態對象,並且希望它可以在(Windows)DLL中使用代碼,那麼您在DllMain期間運行代碼,並且您可能會喜歡的大多數事情確實是不安全的。 – 2010-08-13 20:31:25

9

每一種語言和環境會有所不同,但我@Aaron費舍爾認爲,單身往往對過程的持續時間存在。

在C++中的例子中,使用典型的單成語:

Singleton &get_singleton() 
{ 
    static Singleton singleton; 
    return singleton; 
} 

Singleton實例將被構造的第一次調用該函數時,並且同一個實例將具有它的全局靜態期間調用析構程序關閉時的析構函數階段。

20

像往常一樣,「這取決於」。在任何值得稱讚的操作系統中,當進程退出時,進程中本地使用的所有內存和其他資源將被釋放。你根本不需要擔心這一點。

但是,如果你的單與外面的自己的進程一生(可能是文件,命名的互斥體,或類似的東西)分配資源,那麼你就需要考慮適當的清理。

RAII將幫助你在這裏。如果你有這樣一個場景:

class Tempfile 
{ 
Tempfile() {}; // creates a temporary file 
virtual ~Tempfile(); // close AND DELETE the temporary file 
}; 

Tempfile &singleton() 
{ 
    static Tempfile t; 
    return t; 
} 

...那麼你可以放心,您的臨時文件將被關閉,但刪除了您的應用程序退出。 However, this is NOT thread-safe, and the order of object deletion may not be what you expect or require.

但是,如果你單是這樣

Tempfile &singleton() 
{ 
    static Tempfile *t = NULL; 
    if (t == NULL) 
    t = new Tempfile(); 
    return *t; 
} 

實現......那麼你有不同的情況。你的臨時文件使用的內存將被回收,但該文件不會被刪除,因爲析構函數將不會被調用。

3

你是如何創建對象的?

如果使用全局變量或靜態變量,則會調用析構函數,假定程序正常退出。

例如,程序

#include <iostream> 

class Test 
{ 
    const char *msg; 

public: 

    Test(const char *msg) 
    : msg(msg) 
    {} 

    ~Test() 
    { 
     std::cout << "In destructor: " << msg << std::endl; 
    } 
}; 

Test globalTest("GlobalTest"); 

int main(int, char *argv[]) 
{ 
    static Test staticTest("StaticTest"); 

    return 0; 
} 

打印出

In destructor: StaticTest 
In destructor: GlobalTest 
2

這是民間傳說釋放全局內存分配的應用程序終止前明確。我想大多數人是出於習慣做出來的,因爲我們覺得「忘記」結構有點不好。在C世界裏,任何分配都必須有一個釋放的地方,這是一個對稱規律。如果C++程序員知道並實踐RAII,他們會有不同的想法。

在過去的美好時光裏, AmigaOS有真正的內存泄漏。當你忘記取消分配內存時,它將永遠不會再次訪問,直到系統重置。

我不知道現在有哪些自我尊重的桌面操作系統會讓內存泄漏從應用程序的虛擬地址空間中蔓延開來。當沒有大量記憶簿記時,您的里程可能會因嵌入式設備而異。

0

在像C++這樣沒有垃圾收集的語言中,最好在終止之前進行清理。你可以用析構函數的朋友類來做到這一點。

class Singleton{ 
... 
    friend class Singleton_Cleanup; 
}; 
class Singleton_Cleanup{ 
public: 
    ~Singleton_Cleanup(){ 
     delete Singleton::ptr; 
    } 
}; 

在啓動程序時創建清理類,然後在退出析構函數時將被稱爲清理單例。這可能比讓它進入操作系統更冗長,但它遵循RAII原則,並且取決於單例對象中分配的資源,它可能是必需的。