從 singleton 談 constructor

40
認認 C++ constructor Singleton 認認 Luba Tang 2010/5/11

Upload: tang-luba

Post on 27-May-2015

1.563 views

Category:

Documents


4 download

DESCRIPTION

2012/10/25 新竹碼農

TRANSCRIPT

Page 1: 從 Singleton 談 constructor

認識 C++ constructor ,從 Singleton談起

Luba Tang2010/5/11

Page 2: 從 Singleton 談 constructor

什麼是 singleton?

• 在系統中 唯一 存在 的物件– 唯一性– 存在性

例子1. 集中式資料庫 embeded dbm

2. 棋局中的棋盤3. Register File

Page 3: 從 Singleton 談 constructor

傳統做法 – 全域變數

以棋盤為例子• 在棋盤的 header file 中宣告為全域變數 Chess Board[20][20];

• 在 implementation file 中以外部變數使用 extern Chess Board[20][20];

Page 4: 從 Singleton 談 constructor

/***************************************** * board.h * *****************************************/class BoardType{};static BoardType Board;

/***************************************** * user.h * *****************************************/#include “board.h”extern BoardType Board;

Page 5: 從 Singleton 談 constructor

用全域變數做 singleton 產生的問題

• 在多人同時開發之下,存在性與唯一性不太容易保留

/***************************************** * user.h * *****************************************/#include “board.h”extern BoardType Board;BoardType MyBoard = Board;MyBoard->setChess( black, 20 );

Page 6: 從 Singleton 談 constructor

用全域變數做 singleton 產生的問題

• 你真的確定你的型別一致嗎 ?

// lib1.cunsigned int a;

// lib2.cchar 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

他到底是誰 ?

Page 7: 從 Singleton 談 constructor

Linker 解析 symbol 的順序

• Linker 用下列兩個 rules 解析 symbol– 不能存在兩個強符號– 強符號 > 弱符號– 大空間 > 小空間

• 在 gcc C compiler 當中– 有初始值的 global variable 為強符號– 沒有初始值的 global variable 為弱符號,屬於 COMMON variable 處理

// lib1.cunsigned int a;

// lib2.cchar a;

// main.cextern a; 是 unsigned int

Page 8: 從 Singleton 談 constructor

Global variable 的缺點

1. 不保證存在性 不保證唯一性– 允許 copy, build, delete

2. 難移維護– 使用到棋盤的物件都必須要知道棋盤的類別,

一旦棋盤類別被修改,就必須全部重新修改

Page 9: 從 Singleton 談 constructor

Board *Board::m_pInstance = 0;

C++ 作法class Board{public:

static Board* self() {if( m_pInstance == 0 )

m_pInstance = new Board();return m_pInstance;

}private:

Board();static Board *m_pInstance;

};

提供唯一的進入點如果 instance 沒有 new 過,就 new 一個

新的物件﹔否則就把之前 new 過的物件傳回去

真正的物件必須要宣告為 static ,以保證不會隨著特定物件而消失

前置宣告需要在實作檔中,前置宣告instance ,來初始化指標空間

Page 10: 從 Singleton 談 constructor

Compiler 看 singleton

C++ static member variable– 無論有沒有初始值,都視為 有初始值的

global variable ,為強符號– 強符號具有 linking view 上的唯一性– 強符號具有 linking view 上的存在性

Page 11: 從 Singleton 談 constructor

問題

• 優點– 不必再知道棋盤的類別, Board::self() 就可

以取得棋盤• 缺點

– 我們只保證了 linking view ,尚未保證 run-time behavior

– 存在性尚未保證delete Board::self(); // 合法

Page 12: 從 Singleton 談 constructor

C++ 作法 (2)

class Board{public:

static Board* self();private:

Board();~Board();

private:static Singleton *m_pInstance;

}

將必要函式加入 privateCompiler 會自動幫類別加入這兩個 member function 到 public section 中強制加入到 private section

Page 13: 從 Singleton 談 constructor

問題

• 存在性確保了嗎 ?– YES ,無法任意 delete 、 new 物件

• 唯一性確保了嗎 ?– No ,允許 copy assignment 和 copy

constructor– Board b( *Board::self() ); // 合法,但不是我們想

要的

Page 14: 從 Singleton 談 constructor

C++ 作法 (3)class Board{public:

static Board* self();private:

Board();~Board();Board( const Board& );Board& operator=( const Board& );

private:static Singleton *m_pInstance;

}

將必要函式加入 privateCompiler 會自動幫類別加入這四個 member function 到 public section 中強制加入到 private section

Page 15: 從 Singleton 談 constructor

背景知識 – static local variable

int Fun()

{

static int x = 100;

return ++x;

}

使用 static語意 :生成 function level 的全域變數不同的執行期間,會看到同樣的記憶體空間,同樣的變數

Page 16: 從 Singleton 談 constructor

Meyers’ Singleton

class Board{public:

static Board* self() {static Board Instance;return &Instance;

}private:

Board();~Board();Board( const Board& );Board& operator=( const Board& );

};

提供唯一的進入點static local variable 表示只有在執行第一次的時候會被初始化,相當於之前 if-else 語法

真正的物件利用 static function 的特性,只有在函式被呼叫的時候才會有變數被生成

前置宣告移除前置宣告,程式碼乾乾淨淨

Page 17: 從 Singleton 談 constructor

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& );

};

Page 18: 從 Singleton 談 constructor

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 不會消除– 記憶體空間固定

Page 19: 從 Singleton 談 constructor

從 compiler 觀點看 Meyer’s Singleton

class Board{public:

static Board* self() {static Board Instance;return &Instance;

}private:

Board();~Board();Board( const Board& );Board& operator=( const Board& );

};

何時執行該 constructor?

阿就第一次執行的時候啊 !constructor 不過是另一個

function

Page 20: 從 Singleton 談 constructor

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• 從程式開始到結束

Page 21: 從 Singleton 談 constructor

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

Page 22: 從 Singleton 談 constructor

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& );

};

; 目前 gcc 不給過

Page 23: 從 Singleton 談 constructor

新問題 – Dead Reference Problem

• 在系統結束時,設計不良的 singleton 很有可能被消滅很多次,造成 segmentation fault

Page 24: 從 Singleton 談 constructor

Dead Reference Problem

• 例子– 三個 singleton 物件 – Board, Game, Log– 任何 singleton 發生問題,就會結束整個系

統– 所有物件的 destructor

• Log::self()->printf( “Dead Mesg” );• Clean up member variables

Page 25: 從 Singleton 談 constructor
Page 26: 從 Singleton 談 constructor

C++ 的 static storage 結束方式 - FILO

• Static storage 的 duration 是 main 之前到 main 之後• Static storage 是先呼叫的後結束,在建立物件的時候就

決定了物件消滅的時間

Game::Game()

• Game 出問題• Call Log::self()

1. Log::Log()2. Log::printf()

• Throw exception• Log::~Log();

Board::~Board()

Segmentation fault• Board::~Board()• Log::self()->printf();

Page 27: 從 Singleton 談 constructor

解法一 偵測出 Dead Reference

• 多加一個 static bool m_bDestroy 紀錄是否被消滅過

• 在 Log constructor 中加判斷式,判斷是否被摧毀過

Page 28: 從 Singleton 談 constructor

On Dead Singleton (1/2)

class Log{public:

static Log* self() {if( m_pInstance == 0 ) {

if( m_bDestroy )onDeadReference();

elsecreate();

}return m_pInstance;

}private:

static bool m_bDestory;Log* m_pInstance;

判斷為何 pointer 為 0onDeadReference() 會傳回 errorcreate() 會建立新的 Log 物件

Page 29: 從 Singleton 談 constructor

On Dead Singleton

private:static void create() {

static Log instance;m_pInstance = &instance;

}static void onDeadReference() {

throw std::runtime_error(”Dead Occurs”);}~Singleton() {

m_pInstance = 0;m_bDestory = true;

}};

Meyers’ Singleton標準 constructor 寫法

不使用 delete只需要把 pointer 設為 0 ,不可以 delete m_pInstance ,系統會自動 destroy instance

避免 segmentation fault傳回一個 exception ,把問題丟出去

Page 30: 從 Singleton 談 constructor

On Dead Singleton (1/2)

class Log{public:

static Log* self() {if( m_pInstance == 0 ) {

if( m_bDestroy )onDeadReference();

elsecreate();

}return m_pInstance;

}private:

static bool m_bDestory;static Log* m_pInstance;

判斷為何 pointer 為 0onDeadReference() 會傳回 errorcreate() 會建立新的 Log 物件

Private instance pointer 改為 static

Log* m_pInstance;

Page 31: 從 Singleton 談 constructor

問題

• 優點– 不會發生 segmentation fault ,由錯誤處理物

件負責處理 exception• 缺點

– 需要額外的錯誤處理物件,而錯誤處理物件往往就是問題發生的元兇之一 ( 如本例的 Log 物件 )

– 沒有真正的解決問題,只做到認知問題而已

Page 32: 從 Singleton 談 constructor

Phoenix Singleton

• 解決方法– 希望在 onDeadReference() 中能夠重新

初始化 Log ,讓死掉的 Log 復活,繼續工作

• 關於復活重要的四件事1. 同樣的記憶體位置2. 同樣的記憶體大小3. 不同於死前的記憶體內容4. 必須指定何時再去死一次

Page 33: 從 Singleton 談 constructor

背景資料

• Placement Operator new()– 語法

new(address) class_name();– 意義

1. 以 address 為起點,建立一個 class_name 的物件

2. 不會去註冊 atexit

• atexit– 語法

int atexit( void (*pFun)() );– 意義註冊結束時應該執行哪一個 function, FILO stack

Page 34: 從 Singleton 談 constructor

Phoenix Singleton

private:static void onDeadReference() {

create();new(m_pInstance) Log;atexit(killLog);m_bDestory = false;

}static void killLog() {

m_pInstance->~Log();}

};

取得已死的指標create 雖然無法再取得新物件,但是可以取得死亡物件的指標

在相同位置建立新物件物件狀態會回覆到初始化狀態,無記憶功能

註冊死亡函式其實是間接呼叫 destructor ,凡是有 operator new 就一定要有 atexit

死亡函式間接呼叫 destructor

Page 35: 從 Singleton 談 constructor

完整的 Phoenix Singleton (1/3)

class Log{public:

static Log* self() {if( m_pInstance == 0 ) {

if( m_bDestroy )onDeadReference();

elsecreate();

}return m_pInstance;

}

Page 36: 從 Singleton 談 constructor

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;

}

完整的 Phoenix Singleton (2/3)

Page 37: 從 Singleton 談 constructor

完整的 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;

Page 38: 從 Singleton 談 constructor

可能不是不死鳥,只是 32+ 命怪貓

atexit 只需要支援 “註冊 32 個 functions”

GCC 好家在Libiberty 提供 xatexit ,沒有限制

Page 39: 從 Singleton 談 constructor

Singleton 優劣分析

優點– 好用、好實作– 比 global variable 多很多安全性

缺點– 太好用,常會讓人忘記其毀滅關係上的困難

• 不可以偷懶 ! 要先規劃好生成與毀滅關係,最後才能決定要不要使用 singleton。

• 通常規畫好之後, singleton 數量會很少

Page 40: 從 Singleton 談 constructor

進階主題

• 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