2015-12-21 55 views
4

this thread,以下是說,大約單實例:Singleton實例爲靜態字段與靜態變量在getInstance()方法

靜態變量可以是靜態的,以getInstance()這個函數,或者可以在Singleton類中是靜態的。那裏有一些有趣的折衷。

這些權衡是什麼?我知道,如果聲明爲static函數變量,則在函數首次調用之前,單例不會被構造。我也讀過一些關於線程安全的內容,但並不知道究竟是什麼,或者這兩種方法在這方面有何不同。

兩者之間還有其他的主要區別嗎?哪種方法更好?

在我的具體例子中,我有一個工廠類設置爲一個單例,我將該實例作爲static const字段存儲在類中。我沒有getInstance()方法,而是期望用戶直接訪問實例,如下所示:ItemFactory::factory。默認構造函數是私有的,並且實例是靜態分配的。

附錄:一個想法是多麼好它超載operator()呼籲單身的createItem()方法,這樣Item S能夠像這樣被創建:ItemFactory::factory("id")

+1

請參閱[this](http://stackoverflow.com/questions/8102125/is-local-static-variable-initialization-thread-safe-in-c11)用於靜態函數變量的線程安全性。我會說靜態函數變量是要走的路,因爲通過這種方式你可以確定正確的初始化順序。 – Neijwiert

回答

2

這些折衷是什麼?

這是最重要的考慮因素:

在節目開始時的靜態初始化期間static數據成員被初始化。如果任何static對象取決於單例,那麼將會有static initialization order fiasco

功能本地static對象被初始化時,第一次調用該函數。因爲依賴單身人士的人會調用這個函數,所以單身人士會被適當地初始化,並且不會受到失敗的影響。破壞仍然存在 - 非常微妙的問題。如果靜態對象的析構函數依賴於單例,但該對象的構造函數不包含,那麼最終會導致未定義的行爲。

另外,在第一次調用函數時進行初始化,意味着可以在完成靜態初始化並調用main之後調用該函數。因此,該程序可能產生了多個線程。初始化static本地時可能存在爭用條件,導致構建多個實例。幸運的是,自C++ 11以來,該標準保證了初始化是線程安全的,並且這種折衷在編譯器中不再存在。

線程安全性與static數據成員不成問題。

哪種方法更好?

這取決於您的要求和您支持的標準版本。

2

我投票給靜態函數變量。較新的C++標準要求自動線程安全來初始化這些變量。它已經在GNU C++中實現了大約十年。 Visual Studio 2015也支持這一點。如果你創建一個靜態指針變量來保存對你的單例對象的引用,你將不得不手動處理線程問題。另一方面,如果你製作一個靜態成員指針字段,如下面代碼片段所示,你可以從其他靜態方法改變它,也許在處理請求時重新初始化這個字段與其他實例更改程序配置。但是,下面的代碼片段包含一個錯誤,只是爲了提醒您多線程是多麼的困難。

class ItemFactory { 
static std::atomic_flag initialized = ATOMIC_FLAG_INIT; 
static std::unique_ptr<ItemFactory> theFactoryInstance; 
public: 
static ItemFactory& getInstance() { 
    if (!initialized.test_and_set(std::memory_order_acquire)) { 
    theFactoryInstance = std::make_unique<ItemFactory>(); 
    } 
    return *theFactoryInstance; 
} 
}; 

我不會建議你實現你單身,作爲參加main()函數之前初始化一個全球性的指針變量。線程安全問題會隨着隱式高速緩存一致性消失而消失,但無法以任何精確或便攜的方式控制全局變量的初始化順序。

無論如何,這個選擇並不強制任何永久設計的含義。由於此實例將駐留在您班級的private部分,因此您可以隨時更改它。

我不認爲爲工廠重載operator()是個好主意。 operator()在工廠中具有「執行」語義,它將代表「創建」。

+2

雖然我同意,也許詳細說明*爲什麼靜態函數變量是要走的路,因爲OP要求權衡。 – Neijwiert

+0

'static'類字段有很多不同的應用程序,除了託管單例實例。例如,整個類的屬性,例如動態可重新配置的虛擬功能表替代。 –

+0

這並不真正回答問題*這些權衡是什麼?*和*兩者之間是否還有其他重大差異?哪種方法更好?* – NathanOliver

1

什麼是在C++單身人士的最佳方法?

隱藏它是一個單例並給它賦值語義的事實。

怎麼樣?

所有的單例應該是一個實現細節。這樣,如果你需要改變你實現你的單例的方式(或者如果你確定它不應該是單身人士),你的班級的消費者不需要重構他們的程序。

爲什麼?

因爲現在你的程序永遠不必擔心自己的引用,指針,生命期和whatnot。它只是使用對象的一個​​實例,就好像它是一個值。安全的知識,單身人士會照顧它所具有的任何生命/資源需求。

單身人士在不使用時釋放資源怎麼辦?

沒問題。

下面是一個隱藏在具有值語義的對象的外觀背後的兩種方法的示例。

想象這種使用情況:

auto j1 = jobbie(); 
auto j2 = jobbie(); 
auto j3 = jobbie(); 

j1.log("doh"); 
j2.log("ray"); 
j3.log("me"); 

{ 
    shared_file f; 
    f.log("hello"); 
} 

{ 
    shared_file().log("goodbye"); 
} 

shared_file().log("here's another"); 

shared_file f2; 
{ 
    shared_file().log("no need to reopen"); 
    shared_file().log("or here"); 
    shared_file().log("or even here"); 
} 
f2.log("all done"); 

其中jobbie對象就是一個單一個門面,但shared_file對象要刷新/關閉本身在不使用時。

所以輸出應該是這樣的:

doh 
ray 
me 
opening file 
logging to file: hello 
closing file 
opening file 
logging to file: goodbye 
closing file 
opening file 
logging to file: here's another 
closing file 
opening file 
logging to file: no need to reopen 
logging to file: or here 
logging to file: or even here 
logging to file: all done 
closing file 

我們可以使用的成語,我會打電話給實現「價值語義是-A-門面換單」:

#include <iostream> 
#include <vector> 

// interface 
struct jobbie 
{ 
    void log(const std::string& s); 

private: 
    // if we decide to make jobbie less singleton-like in future 
    // then as far as the interface is concerned the only change is here 
    // and since these items are private, it won't matter to consumers of the class 
    struct impl; 
    static impl& get(); 
}; 

// implementation 

struct jobbie::impl 
{ 
    void log(const std::string& s) { 
     std::cout << s << std::endl; 
    } 
}; 

auto jobbie::get() -> impl& { 
    // 
    // NOTE 
    // now you can change the singleton storage strategy simply by changing this code 
    // alternative 1: 
    static impl _; 
    return _; 

    // for example, we could use a weak_ptr which we lock and store the shared_ptr in the outer 
    // jobbie class. This would give us a shared singleton which releases resources when not in use 

} 

// implement non-singleton interface 

void jobbie::log(const std::string& s) 
{ 
    get().log(s); 
} 

struct shared_file 
{ 
    shared_file(); 

    void log(const std::string& s); 

private: 
    struct impl; 
    static std::shared_ptr<impl> get(); 
    std::shared_ptr<impl> _impl; 
}; 

// private implementation 

struct shared_file::impl { 

    // in a multithreaded program 
    // we require a condition variable to ensure that the shared resource is closed 
    // when we try to re-open it (race condition) 
    struct statics { 
     std::mutex m; 
     std::condition_variable cv; 
     bool still_open = false; 
     std::weak_ptr<impl> cache; 
    }; 

    static statics& get_statics() { 
     static statics _; 
     return _; 
    } 

    impl() { 
     std::cout << "opening file\n"; 
    } 
    ~impl() { 
     std::cout << "closing file\n"; 
     // close file here 
     // and now that it's closed, we can signal the singleton state that it can be 
     // reopened 

     auto& stats = get_statics(); 

     // we *must* use a lock otherwise the compiler may re-order memory access 
     // across the memory fence 
     auto lock = std::unique_lock<std::mutex>(stats.m); 
     stats.still_open = false; 
     lock.unlock(); 
     stats.cv.notify_one(); 
    } 
    void log(const std::string& s) { 
     std::cout << "logging to file: " << s << std::endl; 
    } 
}; 

auto shared_file::get() -> std::shared_ptr<impl> 
{ 
    auto& statics = impl::get_statics(); 

    auto lock = std::unique_lock<std::mutex>(statics.m); 
    std::shared_ptr<impl> candidate; 
    statics.cv.wait(lock, [&statics, &candidate] { 
     return bool(candidate = statics.cache.lock()) 
     or not statics.still_open; 
    }); 
    if (candidate) 
     return candidate; 

    statics.cache = candidate = std::make_shared<impl>(); 
    statics.still_open = true; 
    return candidate; 
} 


// interface implementation 

shared_file::shared_file() : _impl(get()) {} 
void shared_file::log(const std::string& s) { _impl->log(s); } 

// test our class 
auto main() -> int 
{ 
    using namespace std; 

    auto j1 = jobbie(); 
    auto j2 = jobbie(); 
    auto j3 = jobbie(); 

    j1.log("doh"); 
    j2.log("ray"); 
    j3.log("me"); 

    { 
     shared_file f; 
     f.log("hello"); 
    } 

    { 
     shared_file().log("goodbye"); 
    } 

    shared_file().log("here's another"); 

    shared_file f2; 
    { 
     shared_file().log("no need to reopen"); 
     shared_file().log("or here"); 
     shared_file().log("or even here"); 
    } 
    f2.log("all done"); 


    return 0; 
}