談談c++的初始化工作(4) (轉)

amyz發表於2007-08-15
談談c++的初始化工作(4) (轉)[@more@]

  按照慣例,我們簡單回顧一下上次的問題。上次最後說,“ 所有的這些問題,在組合的情況下,也會發生嗎?在陣列初始化時,如果我們希望每個物件的初始化狀態都不同,應該如何做?”,關於組合的情況,我就不回答了,希望您測試過了。但物件陣列的初始化,卻很有趣。
  既然要合適的預設構造才能初始化,又要每個物件的初始化狀態都不同,這就有些為難了。我們有個思路是,在這個合適的建構函式里:),使用鍵盤輸入初始化每個物件。
  則把兩個類的相應建構函式分別改為:
//建構函式1
Shape::Shape(void)
{
 std::cout< std::cin>>x>>y;
}
//建構函式1
Window::Window(void):Shape()
{
 std::cout< std::cin>>width>>height;
}
  如此您不難想象的過程。
  但是,有一個問題是,如果物件陣列的規模比較大,這種輸入的方式比較惹人厭的:)。留個問題,您可以找到另一種高效的初始化方式嗎,能滿足我們這裡的要求?

  我們今天要談的是,特殊需要的初始化,以例項物件須唯一化為例子。
  初始化工作是我們啟動的第一步,我們的需要是各種各樣的。比如,有時,類的例項化要求只能有一個物件(注意,不是我們程式設計師只例項化一個物件,而是類的機制使你只能例項化一個物件!)。

  這樣一個問題,對於初學者,常有這樣的思路:
  首先,類例項化時,同時對物件個數進行計數,在主函式中(或使用類的環境中)寫控制邏輯使得物件只能建立一個。不說是否能實現,這個把類自己的使命交給外部邏輯來完成控制的思路肯定是不可取的。它與oo基本理念是相違背的!
  其次,更多的主張這樣實現。在類的實現上,內定義物件計數器(整型靜態成員變數),然後在建構函式中,進行計數操作。同時判斷計數器值,如果大於1,則退出不構造物件,這樣自然就達到了只能例項化一個物件的要求。

  這種思路聽起來有點意思。但是,事實上怎麼樣呢?我們來實現看看。我們的例子是,要求類Library2,

只能有一個執行例項。

  嘗試類的定義:
#pragma once

class Library2
{
 static int count;//物件計數器
 char book[20];
public:
 Library2(char _book[]);
 virtual ~Library2(void);
 char * GetBook(void);
 static int GetCount(void);

};
#include "libray2.h"
#using
//#include
#include

int Library2::count=0;//初始化為0

//這裡是思路的關鍵
Library2::Library2(char _book[])
{
 if(count==0)
 {
 strncpy(book,_book,sizeof(book));
 count++;
 }
 else return;//(1)如何退出?return?exit?還是其它?
}

Library2::~Library2(void)
{
 count--;
}

char * Library2::GetBook(void)
{
 return book;
}

int Library2::GetCount(void)
{
 return count;
}

  整個思路的關鍵,是在於(1)程式碼的實現。問題是,“則退出不構造物件”,如何才能做到?您來看看,上面的“return”能否做到?換為“exit(0)”呢?您要不能一口拿準,就回頭再看看,好好想一下:)。

  答案是比較顯然的,“return”的作用,無異於什麼都不做的建構函式,物件其實還是建立了的,內容是地址恰所含的值。只是,count沒有來得及記錄。用“exit(0)”呢?退出程式,也不是我們想要的。您可以測試一下,便會明白程式碼的作用。

  繼續沿用這個思路,我們還有“異常機制”沒有使用。既然類Library2只能有一個執行例項,當程式設計師試圖建立多於一個的物件時,我們通知他一個異常發生:你定義的物件個數超過了我們的類的要求。
  對上面的程式碼做如下的改動:

#pragma once
class TooManyObj{ }; // 異常類:Library2物件個數大於1時丟擲異常
class Library2
{
 static int count;
 char book[20];
public:
 Library2(char _book[]);
 virtual ~Library2(void);
 char * GetBook(void);
 static int GetCount(void);

};
//...
Library2::Library2(char _book[])
{
 if(count==0)
 {
 strncpy(book,_book,sizeof(book));
 count++;
 }
 else throw new TooManyObj;//(1)這裡用異常來控制
}
//...

  或許這樣就可行了呢,我們趕緊來測試這個版本的類Library2。測試程式碼為:

#include
#include "libray2.h"

void main()
{
  try
  {
  char *name="C++ Primer"; 
  Library2 library(name);  //第一次例項化

  std::cout<  <

  name="Thinking in C++";
  Library2 library2(name);  //第二次例項化:我們渴望這裡由異常發生
  //但下面的邏輯部分將受什麼影響?
  std::cout<  <

  std::cout< }
 catch(TooManyObj *e)  //處理異常
 {
 std::cout< }
}

  執行,結果是:

Test:library's book:
C++ Primer

Exception of TooManyObj...
Press any key to continue

  很遺憾的是,當異常發生後,就直接轉到異常處理部分了。我們還有許多正常的程式碼,根本沒有機會執行。“則退出不構造物件”,這樣的簡單一句話,因為它違背了初始化的機理,而刻意去追求它,是多麼的費周折。而且,我們總是不由自主的把一部分的邏輯控制寄託於類的使用者來做,這本身就應該避免。

  那麼,正確的解決方案是怎樣的呢?
  答案是我們需要用到一個Singleton。請原諒我這麼不避其繁的寫帖子,因為我覺得,我們遇到問題,總是一步步接近答案的---足夠細心的審視這個過程會使得我們更有能力面對新問題。雖然模式本身只是的總結,但在經驗取得之前,總要有它的探索過程。

  我們重新來審視問題,是“要求類的例項化只能有一個”,很顯然,如果物件可以直接創立的話,如這樣:
  Library2 library(name);

  我想我們已經沒有機會在保證程式不受影響的情況下能合理的“則退出不構造物件”。那麼,到這裡,我們似乎無路可走了!
 
  想一想我們的類,oo特點,難到不能柳暗花明嗎?
  哈哈,那我就把建構函式也封裝為保護的,不讓你直接創立物件。是的,這樣或許會可以,我再想想;既然建構函式封裝起來,那麼我們要生成的物件,肯定不是這個形式:

  Library2 library(name);
那是什麼形式呢?也只有:
  Library2 *plibrary;
了。這樣“裡外”都有了,重點是“中間”:),應該是有某個公共成員函式來初始化指標(你想想,這個函式的呼叫只能靠類本身了,那就是靜態的),又要保證無論幾個這樣的指標,它們只能指向同一個物件的地址。如何辦?靜態變數和靜態函式又要大顯作用了。是的,我們定義一個靜態的指標變數(指向類物件)作為私有成員變數,然後定義一個靜態的成員函式,公共的介面。如果靜態的指標變數為空,就初始化一個物件給它,返回,否則,把它直接返回。這個靜態的成員函式,不是可以作為解決方案的最精彩的一筆?

  看這個模式的實現程式碼:
#pragma once

class Library
{
private:
 static int count;  //僅為了驗證用
 char book[20];
 static Library *_instance;//靜態指標變數
protected:
 Library(void);  //建構函式
 virtual ~Library(void);
public:
 static Library * Instance(void);//這裡才是美妙的
 void SetBook(char _book[]);
 char * GetBook(void);
 static int GetCount(void);
};

#include "library.h"
#using
#include

int Library::count=0;
Library *Library::_instance=0;

Library::Library(void)
{
 count++;
}

Library::~Library(void)
{
 count--;
 //
 delete _instance;
}

//簡單的程式碼,優美的思路,解決了我們的問題
Library * Library::Instance(void)
{
 if(_instance==0)
 {
 _instance=new Library;
 }
 return _instance;
}

void Library::SetBook(char _book[])
{
 strncpy(book,_book,sizeof(book));
}

char * Library::GetBook(void)
{
 return book;
}

int Library::GetCount(void)
{
 return count;
}

  測試程式碼有可能是這樣的:

  //...
  char *name="C++ Primer"; 
  Library *plibrary;
  plibrary=Library::Instance();//看類的定義:這裡發生了什麼?

  plibrary->SetBook(name);

  std::cout<  <GetBook()<

  Library *plibrary2;
  plibrary2=Library::Instance();//看類的定義:這裡又發生了什麼?
  std::cout<  <GetBook()<

  std::cout<  //...

  您再仔細的看一下這個模式,仔細琢磨它的美妙。呵呵,原來我們寫程式,也可以這麼的過癮!如果您對嚴格的文件感興趣,且想了解更多的模式,當然,我推薦您去看“四人幫”那本關於模式的書了:)。

  總的來說,我們的初始化工作,是件十分值得關注的工作。這四次的帖子,小結一下,主要談了:

  第一次,好的初始化過程與靜態成員的初始化;

  第二次,一些變數只有唯一的初始化形式,透過例子,告訴您要特別注意。然後,一步一步,來看資源淺複製的問題。

  第三次,組合與繼承中的初始化問題,最後再說明物件陣列初始化需要注意的地方。主要是繼承的例子。我想澄清一些想法,強調一些觀念。

  第四次,特殊需要的初始化,以例項物件須唯一化為例子。

  這些,也只是展露一角,但希望能拋磚引玉,引起您對之的思考與關注。如果能對您有所幫助,我心裡將是十分高興的。

  最後,謝謝大家的關注。


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/10752019/viewspace-958638/,如需轉載,請註明出處,否則將追究法律責任。

相關文章