1. 程式人生 > 程式設計 >談談C++中的單例

談談C++中的單例

寫C++的時候用到單例,於是很自然的寫出如下的程式碼:

namespace tlanyan {
  class Foo {
  private:
    static Foo* _instance;
    Foo() {}
    // other members

  public:
    static Foo* getInstance() {
      if (_instance == NULL) {
        _instance = new Foo();
      }
      return _instance;
    }
    ~Foo() {
      // clean codes
    }
    // other members and codes
  };
  Foo* Foo::_instance = NULL;
}

程式碼的本意:靜態成員函式getInstance獲取單例指標,並且在解構函式中做一些收尾工作。

執行程式碼後發現解構函式死活不執行,難道一個單例模式都能寫錯?反覆確認,沒發現問題所在,於是上萬能的StackOverflow上找原因。正好有夥計有同樣的疑惑,有哥們給出了一個可行的方案。根據其答案修改程式碼如下:

namespace tlanyan {
  class Foo {
  private:
    Foo() {}
    // other members

  public:
    static Foo& getInstance() {
      static Foo _instance;
      return _instance;
    }
    ~Foo() {
      // clean codes
    }
    // other members and codes
  };
}

對比前一段程式碼,主要改動是移除了靜態指標成員,改用函式內的靜態成員。由於_instance是函式內的靜態成員,在首次呼叫時被初始化(感謝無參建構函式),之後呼叫將略過初始化而執行後續程式碼;函式返回例項的引用,故而每次呼叫得到的是同一個物件,達到了單例的目的;程式執行結束後,例項的解構函式被自動呼叫,解構函式中的程式碼正確執行。

問題解決了,但什麼原因造成第一段單例程式碼的解構函式不執行呢?

這是由於C++持有物件的方式造成的(或者說C++允許程式設計師手動控制記憶體引起)。Java/PHP等帶有回收機制的語言,持有物件的方式是通過指標,程式設計師申請物件後會自動分配記憶體,系統負責跟蹤和回收無用的物件和存在。C++允許開發人員以變數的方式持有物件,例如:Foo foo [= Foo(args)]

。變數初始化後獲得物件的引用,離開作用域後,系統銷燬執行棧,物件自動被析構。C++也可以以指標的形式獲得物件的引用: Foo* foo = new Foo(args)。這種方式分配的物件,需要開發人員手動管理。如果不執行delete,物件和分配的記憶體將一直存在,直到程式退出後,才由作業系統回收。如下程式碼可說明這點:

namespace tlanyan {
  void foo() {
    Foo foo;  // 宣告變數,編譯器會自動初始化變數
    Foo* ptr = new Foo();  // 宣告物件指標,同時為物件開闢記憶體
    // awesome codes
    return;    // 離開作用域, foo物件將被自動析構;如果之前沒有呼叫過delete ptr, ptr指向的物件將一直存在;如果delete ptr,解構函式將被執行。也可以將ptr指向的物件賦值給外層作用域的指標,此時有多個指標指向同一個變數
  }
}

從上面的程式碼可以看出,物件沒有引用計數的情況下,編譯器和系統不敢隨便回收new出來的物件記憶體:多個指標指向同一個物件,delete了物件可能會導致其他程式碼崩潰;釋放記憶體後,其他指標再delete會多次delete同一塊記憶體,引發不可預知風險。

綜上,指標單例解構函式沒有被呼叫的原因是: 自己new的物件,需要自己delete,別指望別人幫你正確呼叫解構函式。

問題的解決方案有以下幾種:

  1. 同StackOverflow上回答,改用變數方式持有單例物件。程式執行結束前會銷燬所有變數,變數的解構函式將被正確呼叫;
  2. 在main函式退出前delete單例。例如增加一個destroy的靜態成員函式,將指標指向的物件銷燬;
  3. 使用auto_ptr/unique_ptr等智慧指標。

如有其它解決方案,歡迎交流指正!

以上就是談談C++中的單例的詳細內容,更多關於c++ 單例的資料請關注我們其它相關文章!