Mais conteúdo relacionado
Semelhante a 從 Singleton 談 constructor (20)
從 Singleton 談 constructor
- 3. 傳統做法 – 全域變數
以棋盤為例子
• 在棋盤的 header file 中宣告為全域變數
Chess Board[20][20];
• 在 implementation file 中以外部變數使用
extern Chess Board[20][20];
- 4. /*****************************************
* board.h *
*****************************************/
class BoardType
{
};
static BoardType Board;
/*****************************************
* user.h *
*****************************************/
#include “board.h”
extern BoardType Board;
- 5. 用全域變數做 singleton 產生的問題
• 在多人同時開發之下,存在性與唯一性不
太容易保留
/*****************************************
* user.h *
*****************************************/
#include “board.h”
extern BoardType Board;
BoardType MyBoard = Board;
MyBoard->setChess( black, 20 );
- 6. 用全域變數做 singleton 產生的問題
• 你真的確定你的型別一致嗎?
// lib1.c // lib2.c
unsigned int a; char a;
// main.c
extern a; 他到底是誰?
[user@locahost]$ gcc –c ./lib1.c && gcc –c ./lib2.c && gcc –c ./main.c
[user@locahost]$ gcc ./lib1.o ./lib2.o ./main.o
- 7. Linker 解析 symbol 的順序
• Linker 用下列兩個 rules 解析 symbol
– 不能存在兩個強符號
– 強符號 > 弱符號
– 大空間 > 小空間
• 在 gcc C compiler 當中
– 有初始值的 global variable 為強符號
– 沒有初始值的 global variable 為弱符號,屬於 COMMON variable處理
// lib1.c // lib2.c
unsigned int a; char a;
// main.c
extern a; 是 unsigned int
- 8. Global variable 的缺點
1. 不保證存在性 不保證唯一性
– 允許 copy, build, delete
2. 難移維護
– 使用到棋盤的物件都必須要知道棋盤的類別,
一旦棋盤類別被修改,就必須全部重新修改
- 9. C++ 作法
提供唯一的進入點
class Board 如果 instance 沒有 new 過,就 new 一個新
{ 的物件﹔否則就把之前 new 過的物件傳回去
public:
static Board* self() {
if( m_pInstance == 0 )
m_pInstance = new Board();
return m_pInstance;
真正的物件
} 必須要宣告為 static,以保證不會
private: 隨著特定物件而消失
Board();
static Board *m_pInstance; 前置宣告
}; 需要在實作檔中,前置宣告
instance,來初始化指標空間
Board *Board::m_pInstance = 0;
- 10. Compiler 看 singleton
C++ static member variable
– 無論有沒有初始值,都視為 有初始值的
global variable,為強符號
– 強符號具有 linking view 上的唯一性
– 強符號具有 linking view 上的存在性
- 11. 問題
• 優點
– 不必再知道棋盤的類別,Board::self() 就可
以取得棋盤
• 缺點
– 我們只保證了 linking view,尚未保證 run-
time behavior
– 存在性尚未保證
delete Board::self(); // 合法
- 12. C++ 作法 (2)
class Board
{
public:
將必要函式加入 private
static Board* self(); Compiler 會自動幫類別加入這兩個
private: member function 到 public section中
強制加入到 private section
Board();
~Board();
private:
static Singleton *m_pInstance;
}
- 13. 問題
• 存在性確保了嗎?
– YES,無法任意 delete、new 物件
• 唯一性確保了嗎?
– No,允許 copy assignment 和 copy
constructor
– Board b( *Board::self() ); // 合法,但不是我們想要的
- 14. C++ 作法 (3)
class Board
{
public: 將必要函式加入 private
Compiler 會自動幫類別加入這四個
static Board* self(); member function 到 public section中
強制加入到 private section
private:
Board();
~Board();
Board( const Board& );
Board& operator=( const Board& );
private:
static Singleton *m_pInstance;
}
- 15. 背景知識 – static local variable
int Fun()
{
static int x = 100;
使用 static
return ++x; 語意:
生成 function level 的全域變數
} 不同的執行期間,會看到同樣的記憶體
空間,同樣的變數
- 16. Meyers’ Singleton
提供唯一的進入點
class Board static local variable 表示只有在執行第一次的
{ 時候會被初始化,相當於之前 if-else 語法
public:
static Board* self() {
static Board Instance;
return &Instance;
} 真正的物件
private: 利用 static function 的特性,只有在函式被呼
Board(); 叫的時候才會有變數被生成
~Board();
Board( const Board& ); 前置宣告
Board& operator=( const Board& ); 移除前置宣告,程式碼乾乾淨
}; 淨
- 17. See Again – Meyers’ Singleton
class Board
{
public:
static Board* self() {
static Board Instance;
return &Instance;
}
private:
Board();
~Board();
Board( const Board& );
Board& operator=( const Board& );
};
- 18. Compiler 來看 Static local variable
normal local variable 稱做 auto-variable,會放在
stack or register 當中
– 通常會被 compiler optimization 消除
– Debugger 和 exception handling 需要有額外的
compensation code 來還原被削除的 auto-variable
Static local variable 被放在 data/bss section 當中
– 大多數狀況下不會被 compiler optimization 消除
– gcc 不會消除
– 記憶體空間固定
- 19. 從 compiler 觀點看 Meyer’s Singleton
class Board
何時執行該
{ constructor?
public:
static Board* self() {
static Board Instance;
return &Instance; 阿就第一次執行的時候啊!
} constructor 不過是另一個
private: function
Board();
~Board();
Board( const Board& );
Board& operator=( const Board& );
};
- 20. Variable = storage type + Duration
C++ Storage
– Static storage
• 用 static 宣告出來的物件,包含 member variable
– Automatic storage
• Auto, register 或者 NOT static, extern 宣告出來的物件
– Dynamic storage
• 用 new 做出來的物件
Storage Duration
– Automatic duration
• 用 register 或 auto 宣告出來的變數,相依於 scope
– Dynamic duration
• 從 new 開始,delete 結束
– Static duration
• 從程式開始到結束
- 21. Static storage initialization 的時機
在 standard 中如是說:
Static storage 在所有其他 initialization 開始之前,就要先 zero initialization
Non-local variable,不論任何 storage type,Static initialization 要在 dynamic
initialization 之前
Zero initialization
Initialization with constant expression
Local static variable 的 initialization 比較複雜
– 允許 compiler 有自己的 implementation
1. 可以在 namespace scope 開始時 initialization
2. 可以在 first time control passes through the declaration 開始時幫忙 initialize
GCC Compiler 當中這樣做:
– Static storage 有值擺 .data
– Static storage 沒值擺 .bss
– Local static POD (C data type) 走 1
– 其他 local static variable 走 2
- 22. See Again – Meyers’ Singleton
class Board
{
public:
static Board* self() {
static Board Instance();
; 目前 gcc 不給過
return &Instance;
}
private:
Board();
~Board();
Board( const Board& );
Board& operator=( const Board& );
};
- 23. 新問題 – Dead Reference Problem
• 在系統結束時,設計不良的 singleton 很
有可能被消滅很多次,造成 segmentation
fault
- 24. Dead Reference Problem
• 例子
– 三個 singleton 物件 – Board, Game, Log
– 任何 singleton 發生問題,就會結束整個系統
– 所有物件的 destructor
• Log::self()->printf( “Dead Mesg” );
• Clean up member variables
- 26. C++ 的static storage結束方式 - FILO
• Static storage 的 duration是main之前到 main 之後
• Static storage 是先呼叫的後結束,在建立物件的時候
就決定了物件消滅的時間
• Game出問題
• Call Log::self()
1. Log::Log()
2. Log::printf()
Game::Game()
• Throw exception
• Log::~Log();
Board::~Board()
• Board::~Board()
• Log::self()->printf(); Segmentation fault
- 27. 解法一 偵測出 Dead Reference
• 多加一個 static bool m_bDestroy 紀錄是
否被消滅過
• 在 Log constructor 中加判斷式,判斷是
否被摧毀過
- 28. On Dead Singleton (1/2)
class Log
判斷為何 pointer 為 0
{
onDeadReference() 會傳回 error
public:
create() 會建立新的 Log 物件
static Log* self() {
if( m_pInstance == 0 ) {
if( m_bDestroy )
onDeadReference();
else
create();
}
return m_pInstance;
}
private:
static bool m_bDestory;
Log* m_pInstance;
- 29. On Dead Singleton
private: Meyers’ Singleton
static void create() { 標準 constructor 寫法
static Log instance;
m_pInstance = &instance;
}
static void onDeadReference() {
throw std::runtime_error(”Dead Occurs”);
}
避免 segmentation fault
~Singleton() { 傳回一個 exception,把問題
m_pInstance = 0; 丟出去
m_bDestory = true;
}
}; 不使用 delete
只需要把 pointer 設為 0,不可以 delete
m_pInstance,系統會自動 destroy instance
- 30. On Dead Singleton (1/2)
class Log 判斷為何 pointer 為 0
{ onDeadReference() 會傳回 error
public: create() 會建立新的 Log 物件
static Log* self() {
if( m_pInstance == 0 ) {
if( m_bDestroy )
onDeadReference();
else
create();
}
return m_pInstance;
}
Private instance pointer改為
private: static
static bool m_bDestory;
static Log* m_pInstance;
Log* m_pInstance;
- 31. 問題
• 優點
– 不會發生 segmentation fault,由錯誤處理物
件負責處理 exception
• 缺點
– 需要額外的錯誤處理物件,而錯誤處理物件
往往就是問題發生的元兇之一 (如本例的 Log
物件)
– 沒有真正的解決問題,只做到認知問題而已
- 32. Phoenix Singleton
• 解決方法
– 希望在 onDeadReference() 中能夠重新初
始化 Log,讓死掉的 Log 復活,繼續工作
• 關於復活重要的四件事
1. 同樣的記憶體位置
2. 同樣的記憶體大小
3. 不同於死前的記憶體內容
4. 必須指定何時再去死一次
- 33. 背景資料
• Placement Operator new()
– 語法
new(address) class_name();
– 意義
1. 以 address 為起點,建立一個 class_name 的物件
2. 不會去註冊 atexit
• atexit
– 語法
int atexit( void (*pFun)() );
– 意義
註冊結束時應該執行哪一個 function, FILO stack
- 34. Phoenix Singleton
取得已死的指標
private: create 雖然無法再取得新
static void 物件,但是可以取得死亡
onDeadReference() {
物件的指標
create();
new(m_pInstance) Log; 在相同位置建立新物件
atexit(killLog); 物件狀態會回覆到初始
化狀態,無記憶功能
m_bDestory = false;
} 註冊死亡函式
static void killLog() { 其實是間接呼叫 destructor,
m_pInstance->~Log(); 凡是有 operator new 就一定
要有 atexit
}
};
死亡函式
間接呼叫destructor
- 35. 完整的 Phoenix Singleton (1/3)
class Log
{
public:
static Log* self() {
if( m_pInstance == 0 ) {
if( m_bDestroy )
onDeadReference();
else
create();
}
return m_pInstance;
}
- 36. 完整的 Phoenix Singleton (2/3)
private:
static void onDeadReference() {
create();
new(m_pInstance) Log;
atexit(killLog);
m_bDestory = false;
}
static void killLog() {
m_pInstance->~Log();
}
static void create() {
static Log instance;
m_pInstance = &instance;
}
- 37. 完整的 Phoenix Singleton (3/3)
private:
~Log() {
m_pInstance = 0;
m_bDestory = true;
}
// constructor, copy constructor, and assignment
…
private:
static bool m_bDestory;
static Log* m_pInstance;
};
// in cpp
bool Log::m_bDestory = false;
Log* Log::m_pInstance = 0;
- 39. Singleton 優劣分析
優點
– 好用、好實作
– 比 global variable 多很多安全性
缺點
– 太好用,常會讓人忘記其毀滅關係上的困難
• 不可以偷懶! 要先規劃好生成與毀滅關係,最
後才能決定要不要使用 singleton。
• 通常規畫好之後,singleton數量會很少
- 40. 進階主題
• Thread-safe singleton
– Is Meyer’s singleton thread-safed?
– If not, please implement one
• Template singleton
– Refer to
template<typename T> llvm::ManagedStatic
• Longevity singleton