程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> 關於C++ >> More Effective C++:避免缺省構造函數

More Effective C++:避免缺省構造函數

編輯:關於C++

缺省構造函數(指沒有參數的構造函數)在C++語言中是一種讓你無中生有的方法。構造函數能初始化對象,而缺省構造函數則可以不利用任何在建立對象時的外部數據就能初始化對象。有時這樣的方法是不錯的。例如一些行為特性與數字相仿的對象被初始化為空值或不確定的值也是合理的,還有比如鏈表、哈希表、圖等等數據結構也可以被初始化為空容器。

但不是所有的對象都屬於上述類型,對於很多對象來說,不利用外部數據進行完全的初始化是不合理的。比如一個沒有輸入姓名的地址簿對象,就沒有任何意義。在一些公司裡,所有的設備都必須標有一個公司ID號碼,所以在建立對象以模型化一個設備時,不提供一個合適的ID號碼,所建立的對象就根本沒有意義。

在一個完美的世界裡,無需任何數據即可建立對象的類可以包含缺省構造函數,而需要數據來建立對象的類則不能包含缺省構造函數。唉!可是我們的現實世界不是完美的,所以我們必須考慮更多的因素。特別是如果一個類沒有缺省構造函數,就會存在一些使用上的限制。

請考慮一下有這樣一個類,它表示公司的設備,這個類包含一個公司的ID代碼,這個ID代碼被強制做為構造函數的參數:

class EquipmentPiece {
 public:
  EquipmentPiece(int IDNumber);
  ...
};

因為EquipmentPiece類沒有一個缺省構造函數,所以在三種情況下使用它,就會遇到問題。第一中情況是建立數組時。一般來說,沒有一種辦法能在建立對象數組時給構造函數傳遞參數。所以在通常情況下,不可能建立EquipmentPiece對象數組:

EquipmentPiece bestPieces[10]; // 錯誤!沒有正確調用
// EquipmentPiece 構造函數
EquipmentPiece *bestPieces =
new EquipmentPiece[10]; // 錯誤!與上面的問題一樣

不過還是有三種方法能回避開這個限制。對於使用非堆數組(non-heap arrays)(即不在堆中給數組分配內存。譯者注)的一種解決方法是在數組定義時提供必要的參數:

int ID1, ID2, ID3, ..., ID10; // 存儲設備ID號的
// 變量
...
EquipmentPiece bestPieces[] = { // 正確, 提供了構造
 EquipmentPiece(ID1), // 函數的參數
 EquipmentPiece(ID2),
 EquipmentPiece(ID3),
 ...,
 EquipmentPiece(ID10)
};

不過很遺憾,這種方法不能用在堆數組(heap arrays)的定義上。一個更通用的解決方法是利用指針數組來代替一個對象數組:

typedef EquipmentPiece* PEP; // PEP 指針指向
//一個EquipmentPiece對象
PEP bestPieces[10]; // 正確, 沒有調用構造函數
PEP *bestPieces = new PEP[10]; // 也正確

在指針數組裡的每一個指針被重新賦值,以指向一個不同的EquipmentPiece對象:

for (int i = 0; i < 10; ++i)
bestPieces[i] = new EquipmentPiece( ID Number );

不過這中方法有兩個缺點,第一你必須刪除數組裡每個指針所指向的對象。如果你忘了,就會發生內存洩漏。第二增加了內存分配量,因為正如你需要空間來容納EquipmentPiece對象一樣,你也需要空間來容納指針。

如果你為數組分配raw memory,你就可以避免浪費內存。使用placement new方法在內存中構造EquipmentPiece對象:

// 為大小為10的數組 分配足夠的內存
// EquipmentPiece 對象; 詳細情況請參見條款8
// operator new[] 函數
void *rawMemory =
operator new[](10*sizeof(EquipmentPiece));
// make bestPieces point to it so it can be treated as an
// EquipmentPiece array
EquipmentPiece *bestPieces =
static_cast(rawMemory);
// construct the EquipmentPiece objects in the memory
// 使用"placement new" (參見條款8)
for (int i = 0; i < 10; ++i)
new (&bestPieces[i]) EquipmentPiece( ID Number );

注意你仍舊得為每一個EquipmentPiece對象提供構造函數參數。這個技術(也稱為數組到指針的思想array-of-pointers)允許你在沒有缺省構造函數的情況下建立一個對象數組。它沒有繞過對構造函數參數的需求,實際上也做不到。如果能做到的話,就不能保證對象被正確初始化。

使用placement new的缺點除了是大多數程序員對它不熟悉外(能使用它就更難了),還有就是當你不想讓它繼續存在使用時,必須手動調用數組對象的析構函數,調用操作符delete[]來釋放 raw memory:

// 以與構造bestPieces對象相反的順序
// 解構它。
for (int i = 9; i >= 0; --i)
bestPieces[i].~EquipmentPiece();
// deallocate the raw memory
operator delete[](rawMemory);

如果你忘記了這個要求或沒有用這個數組刪除方法,那麼你程序的運行將是不可預測的。這是因為直接刪除一個不是用new操作符來分配的內存指針,其結果沒有被定義的。

delete [] bestPieces; // 沒有定義! bestPieces
//不是用new操作符分配的。

有關new、placement new和它們如何與構造函數、析構函數一起使用的更多信息,請見條款8。

對於類裡沒有定義缺省構造函數所造成的第二個問題是它們無法在許多基於模板(template-based)容器類裡使用。因為實例化一個模板時,模板的類型參數應該提供一個缺省構造函數,這是一個常見的要求。這個要求總是來自於模板內部,被建立的模板參數類型數組裡。例如一個數組模板類:

template
class Array {
 public:
  Array(int size);
  ...
 private:
  T *data;
};
template
Array::Array(int size)
{
 data = new T[size]; // 為每個數組元素
 ... //依次調用 T::T()
}

在多數情況下,通過仔細設計模板可以杜絕對缺省構造函數的需求。例如標准的vector模板(生成一個類似於可擴展數組的類)對它的類型參數沒有必須有缺省構造函數的要求。不幸的是,很多模板類沒有以仔細的態度去設計,這樣沒有缺省構造函數的類就不能與許多模板兼容。當C++程序員深入領會了模板設計以後,這樣的問題應該不再那麼突出了。這會花多長時間,完全在於個人的造化。

最後講一下在設計虛基類時所面臨的是要提供缺省構造函數還是不提供缺省構造函數的兩難決策。不提供缺省構造函數的虛基類很難與其進行工作。因為幾乎所有的派生類在實例化時斗必須給虛基類構造函數提供參數。這就要求所有從沒有缺省構造函數的虛基類繼承下來的派生類(無論有多遠)都必須知道並理解提供給虛基類構造函數的參數含義。派生類的作者是不會企盼和喜歡這種規定的。

因為這些強加於沒有缺省構造函數的類上的種種限制,一些人認為所有的類都應該有缺省構造函數,即使缺省構造函數沒有足夠的數據來初始化一個對象。比如這個原則的擁護者會這樣修改EquipmentPiece類:

class EquipmentPiece {
 public:
  EquipmentPiece( int IDNumber = UNSPECIFIED);
  ...
 private:
  static const int UNSPECIFIED; // ID值不確定。
};

這允許這樣建立EquipmentPiece對象

EquipmentPiece e; //這樣合法

這樣的修改使得其他成員函數變得復雜,因為不再能確保EquipmentPiece對象進行有意義的初始化。假設它建立一個因沒有ID而沒有意義的EquipmentPiece對象,那麼大多數成員函數必須檢測ID是否存在。如果不存在ID,它們將必須知道怎麼犯的錯誤。不過這經常是不明晰的,很多代碼實現什麼也沒有提供,只是拋出一個異常或調用一個函數終止程序。當這種情形發生時,很難說提供缺省構造函數而放棄了一種保證機制這種做法是否能提高軟件的總體質量。

提供無意義的缺省構造函數也會影響類的工作效率。如果成員函數必須測試所有的部分是否都被正確地初始化,那麼這些函數的調用者就得為此付出更多的時間。而且還得付出更多的代碼,因為這使得可執行文件或庫變得更大。它們也得在測試失敗的地方放置代碼來處理錯誤。如果一個類的構造函數能夠確保所有的部分被正確初始化,所有這些弊病都能夠避免。缺省構造函數一般不會提供這種保證,所以在它們能使類變得沒有意義時,盡量去避免使用它們。使用這種類的確有一些限制,但是當你使用它時,它也給你提供了一種保證,你能相信這個類被正確地建立和高效地實現。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved