SlideShare une entreprise Scribd logo
1  sur  40
認識 C++ constructor,從Singleton談起


             Luba Tang
             2010/5/11
什麼是 singleton?


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

 例子
 1. 集中式資料庫 embeded dbm
 2. 棋局中的棋盤
 3. Register File
傳統做法 – 全域變數


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


• 在 implementation file 中以外部變數使用
 extern Chess Board[20][20];
/*****************************************
 * board.h                               *
 *****************************************/
class BoardType
{
};
static BoardType Board;


/*****************************************
 * user.h                                *
 *****************************************/
#include “board.h”
extern BoardType Board;
用全域變數做 singleton 產生的問題

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

/*****************************************
 * user.h                                *
 *****************************************/
#include “board.h”
extern BoardType Board;
BoardType MyBoard = Board;
MyBoard->setChess( black, 20 );
用全域變數做 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
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
Global variable 的缺點


1. 不保證存在性 不保證唯一性
 – 允許 copy, build, delete
2. 難移維護
 – 使用到棋盤的物件都必須要知道棋盤的類別,
   一旦棋盤類別被修改,就必須全部重新修改
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;
Compiler 看 singleton


C++ static member variable
  – 無論有沒有初始值,都視為 有初始值的
    global variable,為強符號
  – 強符號具有 linking view 上的唯一性
  – 強符號具有 linking view 上的存在性
問題


• 優點
 – 不必再知道棋盤的類別,Board::self() 就可
   以取得棋盤
• 缺點
 – 我們只保證了 linking view,尚未保證 run-
   time behavior
 – 存在性尚未保證
 delete Board::self(); // 合法
C++ 作法 (2)

class Board
{
public:
                                  將必要函式加入 private
      static Board* self(); Compiler 會自動幫類別加入這兩個
private:                    member function 到 public section中
                            強制加入到 private section
      Board();
      ~Board();
private:
      static Singleton *m_pInstance;
}
問題


• 存在性確保了嗎?
 – YES,無法任意 delete、new 物件
• 唯一性確保了嗎?
 – No,允許 copy assignment 和 copy
   constructor
 – Board b( *Board::self() ); // 合法,但不是我們想要的
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;
}
背景知識 – static local variable


int Fun()
{
  static int x = 100;
                                使用 static
  return ++x;           語意:
                        生成 function level 的全域變數
}                       不同的執行期間,會看到同樣的記憶體
                        空間,同樣的變數
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& );    移除前置宣告,程式碼乾乾淨
};                                           淨
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& );
};
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 不會消除
  – 記憶體空間固定
從 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& );
};
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
       • 從程式開始到結束
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
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& );
};
新問題 – Dead Reference Problem

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


• 例子
 – 三個 singleton 物件 – Board, Game, Log
 – 任何 singleton 發生問題,就會結束整個系統
 – 所有物件的 destructor
   • Log::self()->printf( “Dead Mesg” );
   • Clean up member variables
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
解法一 偵測出 Dead Reference


• 多加一個 static bool m_bDestroy 紀錄是
  否被消滅過
• 在 Log constructor 中加判斷式,判斷是
  否被摧毀過
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;
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
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;
問題


• 優點
 – 不會發生 segmentation fault,由錯誤處理物
   件負責處理 exception
• 缺點
 – 需要額外的錯誤處理物件,而錯誤處理物件
   往往就是問題發生的元兇之一 (如本例的 Log
   物件)
 – 沒有真正的解決問題,只做到認知問題而已
Phoenix Singleton


•    解決方法
    – 希望在 onDeadReference() 中能夠重新初
      始化 Log,讓死掉的 Log 復活,繼續工作
•    關於復活重要的四件事
    1.   同樣的記憶體位置
    2.   同樣的記憶體大小
    3.   不同於死前的記憶體內容
    4.   必須指定何時再去死一次
背景資料

• Placement Operator new()
  – 語法
    new(address) class_name();
  – 意義
    1. 以 address 為起點,建立一個 class_name 的物件
    2. 不會去註冊 atexit
• atexit
  – 語法
    int atexit( void (*pFun)() );
  – 意義
    註冊結束時應該執行哪一個 function, FILO stack
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
完整的 Phoenix Singleton (1/3)

class Log
{
public:
        static Log* self() {
                if( m_pInstance == 0 ) {
                       if( m_bDestroy )
                              onDeadReference();
                       else
                              create();
                }
                return m_pInstance;
        }
完整的 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;
       }
完整的 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;
可能不是不死鳥,只是 32+ 命怪貓


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

GCC 好家在
 Libiberty 提供 xatexit,沒有限制
Singleton 優劣分析

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

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

• 不可以偷懶! 要先規劃好生成與毀滅關係,最
  後才能決定要不要使用 singleton。
• 通常規畫好之後,singleton數量會很少
進階主題


• 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

Contenu connexe

Tendances

Jni攻略之八――操作对象的构造方法
Jni攻略之八――操作对象的构造方法Jni攻略之八――操作对象的构造方法
Jni攻略之八――操作对象的构造方法yiditushe
 
Node.js 异步任务的四种模式
Node.js 异步任务的四种模式Node.js 异步任务的四种模式
Node.js 异步任务的四种模式Stackia Jia
 
05 MapKit and Text Input
05 MapKit and Text Input05 MapKit and Text Input
05 MapKit and Text InputTom Fan
 
Ejb工作原理学习笔记
Ejb工作原理学习笔记Ejb工作原理学习笔记
Ejb工作原理学习笔记yiditushe
 
深入剖析Concurrent hashmap中的同步机制(下)
深入剖析Concurrent hashmap中的同步机制(下)深入剖析Concurrent hashmap中的同步机制(下)
深入剖析Concurrent hashmap中的同步机制(下)wang hongjiang
 
180518 ntut js and node
180518 ntut js and node180518 ntut js and node
180518 ntut js and nodePeter Yi
 
深入剖析Concurrent hashmap中的同步机制(上)
深入剖析Concurrent hashmap中的同步机制(上)深入剖析Concurrent hashmap中的同步机制(上)
深入剖析Concurrent hashmap中的同步机制(上)wang hongjiang
 
Java script closures
Java script closuresJava script closures
Java script closuresskywalker1114
 
iPhone,ios,Object-C基础入门
iPhone,ios,Object-C基础入门iPhone,ios,Object-C基础入门
iPhone,ios,Object-C基础入门Lucien Li
 
C++11综述/新特性描述/Overview of C++11 New Features
C++11综述/新特性描述/Overview of C++11 New FeaturesC++11综述/新特性描述/Overview of C++11 New Features
C++11综述/新特性描述/Overview of C++11 New FeaturesPeien Luo
 
论 Python 与设计模式。
论 Python 与设计模式。论 Python 与设计模式。
论 Python 与设计模式。勇浩 赖
 
Java多线程:驾驭Synchronize的方法
Java多线程:驾驭Synchronize的方法Java多线程:驾驭Synchronize的方法
Java多线程:驾驭Synchronize的方法yiditushe
 
Swift 程序语言介绍
Swift 程序语言介绍Swift 程序语言介绍
Swift 程序语言介绍明 李
 
页游开发中的 Python 组件与模式
页游开发中的 Python 组件与模式页游开发中的 Python 组件与模式
页游开发中的 Python 组件与模式勇浩 赖
 
03 Managing Memory with ARC
03 Managing Memory with ARC03 Managing Memory with ARC
03 Managing Memory with ARCTom Fan
 
冲浪 Object-c
冲浪 Object-c冲浪 Object-c
冲浪 Object-cjeff kit
 
Hash map导致cpu100% 的分析
Hash map导致cpu100% 的分析Hash map导致cpu100% 的分析
Hash map导致cpu100% 的分析wang hongjiang
 
02 Objective-C
02 Objective-C02 Objective-C
02 Objective-CTom Fan
 

Tendances (19)

Jni攻略之八――操作对象的构造方法
Jni攻略之八――操作对象的构造方法Jni攻略之八――操作对象的构造方法
Jni攻略之八――操作对象的构造方法
 
Node.js 异步任务的四种模式
Node.js 异步任务的四种模式Node.js 异步任务的四种模式
Node.js 异步任务的四种模式
 
05 MapKit and Text Input
05 MapKit and Text Input05 MapKit and Text Input
05 MapKit and Text Input
 
Ejb工作原理学习笔记
Ejb工作原理学习笔记Ejb工作原理学习笔记
Ejb工作原理学习笔记
 
深入剖析Concurrent hashmap中的同步机制(下)
深入剖析Concurrent hashmap中的同步机制(下)深入剖析Concurrent hashmap中的同步机制(下)
深入剖析Concurrent hashmap中的同步机制(下)
 
180518 ntut js and node
180518 ntut js and node180518 ntut js and node
180518 ntut js and node
 
深入剖析Concurrent hashmap中的同步机制(上)
深入剖析Concurrent hashmap中的同步机制(上)深入剖析Concurrent hashmap中的同步机制(上)
深入剖析Concurrent hashmap中的同步机制(上)
 
Java script closures
Java script closuresJava script closures
Java script closures
 
iPhone,ios,Object-C基础入门
iPhone,ios,Object-C基础入门iPhone,ios,Object-C基础入门
iPhone,ios,Object-C基础入门
 
ios分享
ios分享ios分享
ios分享
 
C++11综述/新特性描述/Overview of C++11 New Features
C++11综述/新特性描述/Overview of C++11 New FeaturesC++11综述/新特性描述/Overview of C++11 New Features
C++11综述/新特性描述/Overview of C++11 New Features
 
论 Python 与设计模式。
论 Python 与设计模式。论 Python 与设计模式。
论 Python 与设计模式。
 
Java多线程:驾驭Synchronize的方法
Java多线程:驾驭Synchronize的方法Java多线程:驾驭Synchronize的方法
Java多线程:驾驭Synchronize的方法
 
Swift 程序语言介绍
Swift 程序语言介绍Swift 程序语言介绍
Swift 程序语言介绍
 
页游开发中的 Python 组件与模式
页游开发中的 Python 组件与模式页游开发中的 Python 组件与模式
页游开发中的 Python 组件与模式
 
03 Managing Memory with ARC
03 Managing Memory with ARC03 Managing Memory with ARC
03 Managing Memory with ARC
 
冲浪 Object-c
冲浪 Object-c冲浪 Object-c
冲浪 Object-c
 
Hash map导致cpu100% 的分析
Hash map导致cpu100% 的分析Hash map导致cpu100% 的分析
Hash map导致cpu100% 的分析
 
02 Objective-C
02 Objective-C02 Objective-C
02 Objective-C
 

Similaire à 從 Singleton 談 constructor

Keep your code clean
Keep your code cleanKeep your code clean
Keep your code cleanmacrochen
 
C++工程实践
C++工程实践C++工程实践
C++工程实践Shuo Chen
 
改善程序设计技术的50个有效做法
改善程序设计技术的50个有效做法改善程序设计技术的50个有效做法
改善程序设计技术的50个有效做法crasysatan
 
竞赛中C++语言拾遗
竞赛中C++语言拾遗竞赛中C++语言拾遗
竞赛中C++语言拾遗乐群 陈
 
系統架構設計 Android
系統架構設計  Android系統架構設計  Android
系統架構設計 Android健裕 潘
 
Lua 语言介绍
Lua 语言介绍Lua 语言介绍
Lua 语言介绍gowell
 
用Raspberry PI學Linux驅動程式
用Raspberry PI學Linux驅動程式用Raspberry PI學Linux驅動程式
用Raspberry PI學Linux驅動程式Stanley Ho
 
Object-Based Programming Part II
Object-Based Programming Part IIObject-Based Programming Part II
Object-Based Programming Part IIPingLun Liao
 
Java23种设计模式(总结)
Java23种设计模式(总结)Java23种设计模式(总结)
Java23种设计模式(总结)xuanlong282
 
認識 C++11 新標準及使用 AMP 函式庫作平行運算
認識 C++11 新標準及使用 AMP 函式庫作平行運算認識 C++11 新標準及使用 AMP 函式庫作平行運算
認識 C++11 新標準及使用 AMP 函式庫作平行運算建興 王
 
炎炎夏日學 Android 課程 - Part1: Kotlin 語法介紹
炎炎夏日學 Android 課程 -  Part1: Kotlin 語法介紹炎炎夏日學 Android 課程 -  Part1: Kotlin 語法介紹
炎炎夏日學 Android 課程 - Part1: Kotlin 語法介紹Johnny Sung
 
由一个简单的程序谈起――之二
由一个简单的程序谈起――之二由一个简单的程序谈起――之二
由一个简单的程序谈起――之二yiditushe
 
深入理解Andorid重难点
深入理解Andorid重难点深入理解Andorid重难点
深入理解Andorid重难点Bin Shao
 
Java Crash分析(2012-05-10)
Java Crash分析(2012-05-10)Java Crash分析(2012-05-10)
Java Crash分析(2012-05-10)Kris Mok
 
iPhone,ios,Object-c基础入门
iPhone,ios,Object-c基础入门iPhone,ios,Object-c基础入门
iPhone,ios,Object-c基础入门Lucien Li
 
Java SE 8 的 Lambda 連鎖效應 - 語法、風格與程式庫
Java SE 8 的 Lambda 連鎖效應 - 語法、風格與程式庫Java SE 8 的 Lambda 連鎖效應 - 語法、風格與程式庫
Java SE 8 的 Lambda 連鎖效應 - 語法、風格與程式庫Justin Lin
 
淺談C#物件導向與DesignPattern.pdf
淺談C#物件導向與DesignPattern.pdf淺談C#物件導向與DesignPattern.pdf
淺談C#物件導向與DesignPattern.pdfBrian Chou 周家禾
 
Arduino應用系統設計 - Arduino程式快速入門
Arduino應用系統設計 - Arduino程式快速入門Arduino應用系統設計 - Arduino程式快速入門
Arduino應用系統設計 - Arduino程式快速入門吳錫修 (ShyiShiou Wu)
 

Similaire à 從 Singleton 談 constructor (20)

Keep your code clean
Keep your code cleanKeep your code clean
Keep your code clean
 
C++工程实践
C++工程实践C++工程实践
C++工程实践
 
改善程序设计技术的50个有效做法
改善程序设计技术的50个有效做法改善程序设计技术的50个有效做法
改善程序设计技术的50个有效做法
 
竞赛中C++语言拾遗
竞赛中C++语言拾遗竞赛中C++语言拾遗
竞赛中C++语言拾遗
 
系統架構設計 Android
系統架構設計  Android系統架構設計  Android
系統架構設計 Android
 
Lua 语言介绍
Lua 语言介绍Lua 语言介绍
Lua 语言介绍
 
用Raspberry PI學Linux驅動程式
用Raspberry PI學Linux驅動程式用Raspberry PI學Linux驅動程式
用Raspberry PI學Linux驅動程式
 
Object-Based Programming Part II
Object-Based Programming Part IIObject-Based Programming Part II
Object-Based Programming Part II
 
Java23种设计模式(总结)
Java23种设计模式(总结)Java23种设计模式(总结)
Java23种设计模式(总结)
 
認識 C++11 新標準及使用 AMP 函式庫作平行運算
認識 C++11 新標準及使用 AMP 函式庫作平行運算認識 C++11 新標準及使用 AMP 函式庫作平行運算
認識 C++11 新標準及使用 AMP 函式庫作平行運算
 
炎炎夏日學 Android 課程 - Part1: Kotlin 語法介紹
炎炎夏日學 Android 課程 -  Part1: Kotlin 語法介紹炎炎夏日學 Android 課程 -  Part1: Kotlin 語法介紹
炎炎夏日學 Android 課程 - Part1: Kotlin 語法介紹
 
由一个简单的程序谈起――之二
由一个简单的程序谈起――之二由一个简单的程序谈起――之二
由一个简单的程序谈起――之二
 
深入理解Andorid重难点
深入理解Andorid重难点深入理解Andorid重难点
深入理解Andorid重难点
 
Java Crash分析(2012-05-10)
Java Crash分析(2012-05-10)Java Crash分析(2012-05-10)
Java Crash分析(2012-05-10)
 
Rootkit 101
Rootkit 101Rootkit 101
Rootkit 101
 
iPhone,ios,Object-c基础入门
iPhone,ios,Object-c基础入门iPhone,ios,Object-c基础入门
iPhone,ios,Object-c基础入门
 
Java SE 8 的 Lambda 連鎖效應 - 語法、風格與程式庫
Java SE 8 的 Lambda 連鎖效應 - 語法、風格與程式庫Java SE 8 的 Lambda 連鎖效應 - 語法、風格與程式庫
Java SE 8 的 Lambda 連鎖效應 - 語法、風格與程式庫
 
Nio trick and trap
Nio trick and trapNio trick and trap
Nio trick and trap
 
淺談C#物件導向與DesignPattern.pdf
淺談C#物件導向與DesignPattern.pdf淺談C#物件導向與DesignPattern.pdf
淺談C#物件導向與DesignPattern.pdf
 
Arduino應用系統設計 - Arduino程式快速入門
Arduino應用系統設計 - Arduino程式快速入門Arduino應用系統設計 - Arduino程式快速入門
Arduino應用系統設計 - Arduino程式快速入門
 

從 Singleton 談 constructor

  • 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.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
  • 25.
  • 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;
  • 38. 可能不是不死鳥,只是 32+ 命怪貓 atexit 只需要支援 “註冊 32 個 functions” GCC 好家在 Libiberty 提供 xatexit,沒有限制
  • 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