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