Có lẽ bạn nên đọc sách của Alexandrescu.
Về tĩnh cục bộ, tôi đã không sử dụng Visual Studio trong một thời gian, nhưng khi biên dịch với Visual Studio 2003, có một địa phương tĩnh phân bổ cho mỗi DLL ... nói về một cơn ác mộng của gỡ lỗi, tôi sẽ nhớ rằng một trong một thời gian:/
1. Lifetime của một Singleton
vấn đề chính về độc thân là công tác quản lý suốt đời.
Nếu bạn cố gắng sử dụng đối tượng, bạn cần phải còn sống và đá. Vấn đề do đó xuất phát từ cả khởi tạo và hủy diệt, đó là một vấn đề phổ biến trong C++ với globals.
Việc khởi tạo thường là điều dễ nhất để sửa. Như cả hai phương pháp đề xuất, nó đủ đơn giản để khởi tạo khi sử dụng lần đầu tiên.
Sự hủy diệt có phần tinh tế hơn một chút. các biến toàn cầu bị hủy theo thứ tự ngược lại mà chúng được tạo ra. Vì vậy, trong trường hợp tĩnh địa phương, bạn không thực sự kiểm soát mọi thứ ....
2. Local tĩnh
struct A
{
A() { B::Instance(); C::Instance().call(); }
};
struct B
{
~B() { C::Instance().call(); }
static B& Instance() { static B MI; return MI; }
};
struct C
{
static C& Instance() { static C MI; return MI; }
void call() {}
};
A globalA;
vấn đề ở đây là gì? Hãy kiểm tra thứ tự mà trong đó các constructor và destructors được gọi.
Thứ nhất, giai đoạn xây dựng:
A globalA;
được thực thi, A::A()
được gọi
A::A()
cuộc gọi B::B()
A::A()
cuộc gọi C::C()
Nó hoạt động tốt, bởi vì chúng ta khởi tạo B
và C
phiên bản trên linh sam t truy cập.
Thứ hai, giai đoạn phá hủy:
C::~C()
được gọi là bởi vì nó là xây dựng 3
B::~B()
cuối cùng được gọi là ... oups, nó cố gắng để truy cập C
's dụ!
Chúng tôi do đó có hành vi không xác định tại hủy diệt, hum ...
3. Chiến lược mới
Ý tưởng ở đây là đơn giản.toàn cầu được xây dựng-in được khởi tạo trước khi globals khác, vì vậy con trỏ của bạn sẽ được thiết lập để 0
trước khi bất kỳ mã bạn đã viết sẽ được gọi, nó đảm bảo rằng các thử nghiệm:
S& S::Instance() { if (MInstance == 0) MInstance = new S(); return *MInstance; }
sẽ thực sự kiểm tra hay không phải là trường hợp chính xác.
Tuy nhiên đã có lúc được nói, có một rò rỉ bộ nhớ ở đây và tồi tệ nhất là một destructor mà không bao giờ được gọi. Giải pháp tồn tại và được chuẩn hóa. Đây là cuộc gọi đến hàm atexit
.
Chức năng atexit
cho phép bạn chỉ định một hành động để thực thi trong khi tắt chương trình. Với điều đó, chúng ta có thể viết một singleton alright:
// in s.hpp
class S
{
public:
static S& Instance(); // already defined
private:
static void CleanUp();
S(); // later, because that's where the work takes place
~S() { /* anything ? */ }
// not copyable
S(S const&);
S& operator=(S const&);
static S* MInstance;
};
// in s.cpp
S* S::MInstance = 0;
S::S() { atexit(&CleanUp); }
S::CleanUp() { delete MInstance; MInstance = 0; } // Note the = 0 bit!!!
Đầu tiên, hãy tìm hiểu thêm về atexit
. Chữ ký là int atexit(void (*function)(void));
, tức là nó chấp nhận một con trỏ đến một hàm mà không có gì là đối số và cũng không trả về gì cả.
Thứ hai, cách hoạt động? Vâng, chính xác như trường hợp sử dụng trước đó: lúc khởi tạo nó xây dựng một chồng các con trỏ để hoạt động để gọi và hủy diệt nó làm trống ngăn xếp một mục tại một thời điểm. Vì vậy, trong thực tế, các chức năng được gọi là trong một Last-In First-Out thời trang.
Điều gì xảy ra ở đây?
Xây dựng về tiếp cận đầu tiên (khởi là tốt), tôi đăng ký phương pháp CleanUp
cho thời gian thoát
thời gian Exit: phương pháp CleanUp
được gọi. Nó phá hủy đối tượng (do đó chúng ta có thể làm việc hiệu quả trong destructor) và thiết lập lại con trỏ thành 0
để báo hiệu nó.
gì xảy ra nếu (như trong ví dụ với A
, B
và C
) Tôi kêu gọi các thể hiện của một đối tượng đã bị phá hủy? Vâng, trong trường hợp này, kể từ khi tôi đặt trở lại con trỏ đến 0
Tôi sẽ xây dựng lại một singleton tạm thời và chu kỳ bắt đầu một lần nữa. Nó sẽ không tồn tại lâu mặc dù kể từ khi tôi depiling stack của tôi.
Alexandrescu gọi nó là Phoenix Singleton
khi nó khôi phục từ tro của nó nếu cần sau khi bị phá hủy.
Một giải pháp thay thế khác là có cờ tĩnh và đặt nó thành destroyed
trong khi dọn dẹp và cho người dùng biết rằng nó không nhận được bản sao của singleton, ví dụ bằng cách trả về một con trỏ rỗng. Vấn đề duy nhất tôi có với trả về một con trỏ (hoặc tham chiếu) là bạn muốn tốt hơn hy vọng không ai đủ ngu ngốc để gọi delete
vào nó:/
4. Pattern monoid
Vì chúng ta đang nói về Singleton
Tôi nghĩ đã đến lúc giới thiệu mẫu Monoid
. Về bản chất, nó có thể được xem như một trường hợp thoái hóa của mẫu Flyweight
hoặc sử dụng Proxy
trên Singleton
.
Mẫu Monoid
rất đơn giản: tất cả các phiên bản của lớp đều có chung trạng thái.
tôi sẽ có cơ hội để tiếp xúc với việc thực hiện không-Phoenix :)
class Monoid
{
public:
void foo() { if (State* i = Instance()) i->foo(); }
void bar() { if (State* i = Instance()) i->bar(); }
private:
struct State {};
static State* Instance();
static void CleanUp();
static bool MDestroyed;
static State* MInstance;
};
// .cpp
bool Monoid::MDestroyed = false;
State* Monoid::MInstance = 0;
State* Monoid::Instance()
{
if (!MDestroyed && !MInstance)
{
MInstance = new State();
atexit(&CleanUp);
}
return MInstance;
}
void Monoid::CleanUp()
{
delete MInstance;
MInstance = 0;
MDestroyed = true;
}
lợi ích là gì? Nó ẩn một thực tế là nhà nước được chia sẻ, nó ẩn các Singleton
.
- Nếu bạn đã bao giờ cần phải có 2 trạng thái khác nhau, có thể là bạn sẽ quản lý để làm điều đó mà không thay đổi mỗi dòng mã mà sử dụng nó (thay thế
Singleton
bởi một cuộc gọi đến một Factory
ví dụ)
- Nodoby sẽ gọi
delete
trên bản sao của bạn, vì vậy bạn thực sự quản lý nhà nước và ngăn ngừa tai nạn ... bạn không thể làm gì nhiều chống lại người dùng độc hại anyway!
- Bạn kiểm soát quyền truy cập vào các singleton, vì vậy trong trường hợp nó được gọi là sau khi nó bị phá hủy, bạn có thể xử lý nó một cách chính xác (không làm gì cả, đăng nhập, vv ...)
5. từ cuối
Hoàn thành như điều này có vẻ như, tôi muốn chỉ ra rằng tôi đã vui vẻ lướt qua bất kỳ vấn đề đa luồng ... đọc hiện đại của Alexandrescu C++ để tìm hiểu thêm!
Nếu bạn thực sự quan tâm, "Thiết kế C++ hiện đại" của Alexandrescu có cả một chương nhằm cố gắng làm cho người độc thân an toàn và chính xác, khám phá nhiều góc tối của ngôn ngữ. Theo tôi, nó có thể được tóm tắt là "chỉ không". –
@Mike - tham khảo tuyệt vời và tôi đồng ý hoàn toàn. –