Your SlideShare is downloading. ×
從 Singleton 談 constructor
Upcoming SlideShare
Loading in...5
×

Thanks for flagging this SlideShare!

Oops! An error has occurred.

×
Saving this for later? Get the SlideShare app to save on your phone or tablet. Read anywhere, anytime – even offline.
Text the download link to your phone
Standard text messaging rates apply

從 Singleton 談 constructor

1,439
views

Published on

2012/10/25 新竹碼農

2012/10/25 新竹碼農


0 Comments
16 Likes
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total Views
1,439
On Slideshare
0
From Embeds
0
Number of Embeds
0
Actions
Shares
0
Downloads
38
Comments
0
Likes
16
Embeds 0
No embeds

Report content
Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
No notes for slide

Transcript

  • 1. 認識 C++ constructor,從Singleton談起 Luba Tang 2010/5/11
  • 2. 什麼是 singleton?• 在系統中 唯一 存在 的物件 – 唯一性 – 存在性 例子 1. 集中式資料庫 embeded dbm 2. 棋局中的棋盤 3. Register File
  • 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.cunsigned int a; char a;// main.cextern 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.cunsigned int a; char a;// main.cextern a; 是 unsigned int
  • 8. Global variable 的缺點1. 不保證存在性 不保證唯一性 – 允許 copy, build, delete2. 難移維護 – 使用到棋盤的物件都必須要知道棋盤的類別, 一旦棋盤類別被修改,就必須全部重新修改
  • 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 sectionprivate: Board(); ~Board(); Board( const Board& ); Board& operator=( const Board& );private: static Singleton *m_pInstance;}
  • 15. 背景知識 – static local variableint 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’ Singletonclass 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 Singletonclass 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’ Singletonclass 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
  • 25. 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
  • 26. 解法一 偵測出 Dead Reference• 多加一個 static bool m_bDestroy 紀錄是 否被消滅過• 在 Log constructor 中加判斷式,判斷是 否被摧毀過
  • 27. On Dead Singleton (1/2)class Log 判斷為何 pointer 為 0{ onDeadReference() 會傳回 errorpublic: 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;
  • 28. On Dead Singletonprivate: 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
  • 29. On Dead Singleton (1/2)class Log 判斷為何 pointer 為 0{ onDeadReference() 會傳回 errorpublic: 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;
  • 30. 問題• 優點 – 不會發生 segmentation fault,由錯誤處理物 件負責處理 exception• 缺點 – 需要額外的錯誤處理物件,而錯誤處理物件 往往就是問題發生的元兇之一 (如本例的 Log 物件) – 沒有真正的解決問題,只做到認知問題而已
  • 31. Phoenix Singleton• 解決方法 – 希望在 onDeadReference() 中能夠重新初 始化 Log,讓死掉的 Log 復活,繼續工作• 關於復活重要的四件事 1. 同樣的記憶體位置 2. 同樣的記憶體大小 3. 不同於死前的記憶體內容 4. 必須指定何時再去死一次
  • 32. 背景資料• Placement Operator new() – 語法 new(address) class_name(); – 意義 1. 以 address 為起點,建立一個 class_name 的物件 2. 不會去註冊 atexit• atexit – 語法 int atexit( void (*pFun)() ); – 意義 註冊結束時應該執行哪一個 function, FILO stack
  • 33. 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
  • 34. 完整的 Phoenix Singleton (1/3)class Log{public: static Log* self() { if( m_pInstance == 0 ) { if( m_bDestroy ) onDeadReference(); else create(); } return m_pInstance; }
  • 35. 完整的 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; }
  • 36. 完整的 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 cppbool Log::m_bDestory = false;Log* Log::m_pInstance = 0;
  • 37. 可能不是不死鳥,只是 32+ 命怪貓atexit 只需要支援 “註冊 32 個 functions”GCC 好家在 Libiberty 提供 xatexit,沒有限制
  • 38. Singleton 優劣分析 優點 – 好用、好實作 – 比 global variable 多很多安全性 缺點 – 太好用,常會讓人忘記其毀滅關係上的困難• 不可以偷懶! 要先規劃好生成與毀滅關係,最 後才能決定要不要使用 singleton。• 通常規畫好之後,singleton數量會很少
  • 39. 進階主題• 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

×