More Effective C++ 條款26(下) (轉)

worldblog發表於2007-12-11
More Effective C++ 條款26(下) (轉)[@more@]

條款26:限制某個類所能產生的數量(下) 

允許物件來去自由:namespace prefix = o ns = "urn:schemas--com::office" />

我們知道如何設計只允許建立一個例項的類,我們知道跟蹤特定類的物件數量的工作是複雜的,因為在三種不同的環境中都可能物件的構造,我們知道消除物件計數中混亂現象的方法是把建構函式宣告為private。還有最後一點值得我們注意:使用thePrinter函式封裝對單個物件的訪問,以便把Printer物件的數量限制為一個,這樣做的同時也會讓我們在每一次執行時只能使用一個Printer物件。導致我們不能這樣編寫程式碼:

建立 Printer 物件 p1;

 

使用 p1;

 

釋放 p1;

 

建立Printer物件p2;

 

使用 p2;

 

釋放 p2;

 

...

這種設計在同一時間裡沒有例項化多個Printer物件,而是在程式的不同部分使用了不同的Printer物件。不允許這樣編寫有些不合理。畢竟我們沒有違反只能存在一個printer的。就沒有辦法使它合法化麼?

當然有。我們必須把先前使用的物件計數的程式碼與剛才看到的偽建構函式程式碼合併在一起:

class Printer {

public:

  class TooManys{};

 

  // 偽建構函式

  static Printer * makePrinter();

 

~Printer();

 

  void submitJob(const PrintJob& job);

  void reset();

  void performSelfTest();

  ...

 

private:

  static size_t numObjects;

 

  Printer();

 

  Printer(const Printer& rhs);  //我們不定義這個函式

};  //因為不允許

  //進行複製

  // (參見Effective C++條款27)

 

// Obligatory definition of class static

size_t Printer::numObjects = 0;

 

Printer::Printer()

{

  if (numObjects >= 1) {

  throw TooManyObjects();

  }

 

  繼續執行正常的建構函式;

 

  ++numObjects;

}

 

Printer * Printer::makePrinter()

{ return new Printer; }

當需要的物件過多時,會丟擲異常,如果你認為這種方式給你的感覺是unreasonably harsh,你可以讓偽建構函式返回一個空指標。當然客戶端在使用之前應該進行檢測。

除了客戶端必須呼叫偽建構函式,而不是真正的建構函式之外,它們使用Printer類就象使用其他類一樣:

Printer p1;  // 錯誤! 預設建構函式是

  // private

 

Printer *p2 =

  Printer::makePrinter();  // 正確, 間接呼叫

  // 預設建構函式

 

Printer p3 = *p2;  // 錯誤! 複製建構函式是

  // private

 

p2->performSelfTest();  // 所有其它的函式都可以

p2->reset();  // 正常呼叫

 

...

 

delete p2;  // 避免洩漏,如果

  // p2 是一個 auto_ptr,

  // 就不需要這步。

這種技術很容易推廣到限制物件為任何數量上。我們只需把hard-wired常量值1改為根據某個類而確定的數量,然後消除複製物件的約束。例如,下面這個經過修改的Printer類的程式碼實現,最多允許10個Printer物件存在:

class Printer {

public:

  class TooManyObjects{};

 

  // 偽建構函式

  static Printer * makePrinter();

  static Printer * makePrinter(const Printer& rhs);

 

  ...

 

private:

  static size_t numObjects;

  static const size_t maxObjects = 10;  // 見下面解釋

 

  Printer();

  Printer(const Printer& rhs);

};

 

// Obligatory definitions of class statics

size_t Printer::numObjects = 0;

const size_t Printer::maxObjects;

 

Printer::Printer()

{

  if (numObjects >= maxObjects) {

  throw TooManyObjects();

  }

 

  ...

 

}

 

Printer::Printer(const Printer& rhs)

{

  if (numObjects >= maxObjects) {

  throw TooManyObjects();

  }

 

  ...

 

}

 

Printer * Printer::makePrinter()

{ return new Printer; }

 

Printer * Printer::makePrinter(const Printer& rhs)

{ return new Printer(rhs); }

如果你的不能編譯上述類中Printer::maxObjects的宣告,這絲毫也不奇怪。特別是應該做好準備,編譯器不能編譯把10做為初值賦給這個變數這條語句。給static const成員(例如int, char, enum等等)確定初值的功能是最近才加入到C++中的,所以一些編譯器還不允許這樣編寫。如果沒有及時你的編譯器,可以把maxObjects宣告為在一個private內匿名列舉型別裡的列舉元素,

class Printer {

private:

  enum { maxObjects = 10 };  // 在類中,

  ...  // maxObjects為常量10

};

 

或者象non-const static成員一樣初始化static常量:

 

class Printer {

private:

  static const size_t maxObjects;  // 沒有賦給初值

 

  ...

 

};

 

// 放在一個程式碼實現的中

const size_t Printer::maxObjects = 10;

後面這種方法與原來的方法有一樣的效果,但是顯示地確定初值能讓其他程式設計師更容易理解。當你的編譯器支援在類定義中給const static成員賦初值的功能時,你應該儘可能地利用這個功能。

一個具有物件計數功能的基類

把初始化靜態成員撇在一邊不說,上述的方法使用起來就像咒語一樣靈驗,但是另一方面它也有些繁瑣。如果我們有大量像Printer需要限制例項數量的類,就必須一遍又一遍地編寫一樣的程式碼,每個類編寫一次。這將會使大腦變得麻木。應該有一種方法能夠自動處理這些事情。難道沒有方法把例項計數的思想封裝在一個類裡嗎?

我們很容易地能夠編寫一個具有例項計數功能的基類,然後讓像Printer這樣的類從該基類繼承,而且我們能做得更好。我們使用一種方法封裝全部的計數功能,不但封裝維護例項計數器的函式,而且也封裝例項計數器本身。(當我們在條款29中測試引用計數時,將看到需要同樣的技術。有關這種設計的測試細節,參見我的文章counting objects)

Printer類的計數器是靜態變數numObjects,我們應該把變數放入例項計數類中。然而也需要確保每個進行例項計數的類都有一個相互隔離的計數器。使用計數類别範本可以自動生成適當數量的計數器,因為我們能讓計數器成為從模板中生成的類的靜態成員:

template

class Counted {

public:

  class TooManyObjects{};  // 用來丟擲異常

 

  static int objectCount() { return numObjects; }

 

protected:

  Counted();

  Counted(const Counted& rhs);

 

  ~Counted() { --numObjects; }

 

private:

  static int numObjects;

  static const size_t maxObjects;

 

  void init();  // 避免建構函式的

};  // 程式碼重複

 

template

Counted::Counted()

{ init(); }

 

template

Counted::Counted(const Counted&)

{ init(); }

 

template

void Counted::init()

{

  if (numObjects >= maxObjects) throw TooManyObjects();

  ++numObjects;

}

從這個模板生成的類僅僅能被做為基類使用,因此建構函式和解構函式被宣告為protected。注意private成員函式init用來避免兩個Counted建構函式的語句重複。

現在我們能修改Printer類,這樣使用Counted模板:

class Printer: private Counted {

public:

  // 偽建構函式

  static Printer * makePrinter();

  static Printer * makePrinter(const Printer& rhs);

 

  ~Printer();

 

  void submitJob(const PrintJob& job);

  void reset();

  void performSelfTest();

  ...

 

  using Counted::objectCount;  // 參見下面解釋

  using Counted::TooManyObjects;  // 參見下面解釋

 

private:

  Printer();

  Printer(const Printer& rhs);

};

Printer使用了Counter模板來跟蹤存在多少Printer物件,坦率地說,除了Printer的編寫者,沒有人關心這個事實。它的實現細節最好是private,這就是為什麼這裡使用private繼承的原因(參見Effective C++條款42)。另一種方法是在Printer和counted之間使用public繼承,但是我們必須給Counted類一個虛擬解構函式。(否則如果有人透過Counted*指標刪除一個Printer物件,我們就有導致物件行為不正確的風險——參見Effective C++條款14。)條款24已經說得很明白了,在Counted中存在虛擬函式,幾乎肯定影響從Counted繼承下來的物件的大小和佈局。我們不想引入這些額外的負擔,所以使用private繼承來避免這些負擔。

Counted所做的大部分工作對於Printer的客戶端來說都是隱藏的,但是這些客戶端可能很想知道有當前多少Printer物件存在。Counted模板提供了objectCount函式,用來提供這種資訊,但是因為我們使用private繼承,這個函式在Printer類中成為了private。為了恢復該函式的public訪問權,我們使用using宣告:

class Printer: private Counted {

public:

  ...

  using Counted::objectCount; // 讓這個函式對於Printer

  //是public

  ... 

};

這樣做是合乎語法規則的,但是如果你的編譯器不支援名稱空間,編譯器就不允許這樣做。如果這樣的話,你應使用老式的訪問權宣告語法:

class Printer: private Counted {

public:

  ...

  Counted::objectCount;  // 讓objectCount

  // 在Printer中是public

  ...

 

};

這種更傳統的語法與uning宣告具有相同的含義。但是我們不贊成這樣做。TooManyObjects應該也應用同樣的方式來處理,因為Printer的客戶端如果要捕獲這種異常型別,它們必須有能力訪問TooManyObjects。.

當Printer繼承Counted時,它可以忘記有關物件計數的事情。編寫Printer類時根本不用考慮物件計數,就好像有其他人會為它計數一樣。Printer的建構函式可以是這樣的:

Printer::Printer()

{

  進行正常的建構函式執行

}

這裡有趣的不是你所見到的東西,而是你看不到的東西。不檢測物件的數量就好像限制將被超過,完建構函式後也不增加存在物件的數目。所有這些現在都由Counted的建構函式來處理,因為Counted是Printer的基類,我們知道Counted的建構函式總在Printer的前面被呼叫。如果建立過多的物件,Counted的建構函式就會丟擲異常,甚至都沒有呼叫Printer的建構函式。

最後還有一點需要注意,必須定義Counted內的靜態成員。對於numObjects來說,這很容易——我們只需要在Counted的實現檔案裡定義它即可:

template  // 定義numObjects

int Counted::numObjects;  // 自動把它初始化為0

對於maxObjects來說,則有一些技巧。我們應該把它初始化為什麼值呢?如果你想允許建立10個printer物件,我們應該初始化Counted::maxObjects為10。另一方面如果我們向允許建立16個檔案描述符物件,我們應該初始化Counted::maxObjects為16。到底應該怎麼做呢?

簡單的方法就是什麼也不做。我們不對maxObject進行初始化。而是讓此類的客戶端提供合適的初始化。Printer的作者必須把這條語句加入到一個實現檔案裡:

const size_t Counted::maxObjects = 10;

同樣FileDescriptor的作者也得加入這條語句:

const size_t Counted::maxObjects = 16;

如果這些作者忘了對maxObjects進行初始化,會發生什麼情況呢?很簡單:連線時會發生錯誤,因為maxObjects沒有被定義。如果我們提供了充分的文件對Counted客戶端說明了需求,他們會回去加上這個必須的初始化。

 

譯者注:

translation unit - a file presented to a complier with an object file produced as a result.

linkage - refers to whether a name is visible only ins or also outside its translation unit.


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

相關文章