在討論內容之前先看下面的一個程序,它用來進行新聞報道工作,每一條新聞報道都由文字或圖片組成。
[cpp]
<SPAN style="FONT-SIZE: 18px">class NLComponent { //用於 newsletter components
public: // 的抽象基類
... //包含至少一個純虛函數
};
class TextBlock: public NLComponent {
public:
... // 不包含純虛函數
};
class Graphic: public NLComponent {
public:
... // 不包含純虛函數
};
class NewsLetter { // 一個 newsletter 對象
public: // 由NLComponent 對象
... // 的鏈表組成
private:
list<NLComponent*> components;
};
</SPAN>
class NLComponent { //用於 newsletter components
public: // 的抽象基類
... //包含至少一個純虛函數
};
class TextBlock: public NLComponent {
public:
... // 不包含純虛函數
};
class Graphic: public NLComponent {
public:
... // 不包含純虛函數
};
class NewsLetter { // 一個 newsletter 對象
public: // 由NLComponent 對象
... // 的鏈表組成
private:
list<NLComponent*> components;
};
類結構:
NewLetter在不運行時存儲在磁盤上,所以讓它的構造函數帶有istream參數,可以從流中讀取信息。
class NewsLetter {
public:
NewsLetter(istream& str);
...
};
偽代碼:
NewsLetter::NewsLetter(istream& str)
{
while (str) {
從str讀取下一個component對象;
把對象加入到newsletter的 components對象的鏈表中去;
}
}
或者把這一讀數據的功能單獨抽象出來,弄一個readComponent,例如
[cpp]
<SPAN style="FONT-SIZE: 18px">class NewsLetter {
public:
...
private:
// 為建立下一個NLComponent對象從str讀取數據,
// 建立component 並返回一個指針。
static NLComponent * readComponent(istream& str);//靜態函數
...
};
NewsLetter::NewsLetter(istream& str)
{
while (str) {
// 把readComponent返回的指針添加到components鏈表的最後,
// "push_back" 一個鏈表的成員函數,用來在鏈表最後進行插入操作。
components.push_back(readComponent(str));//在這調用
}
}</SPAN>
class NewsLetter {
public:
...
private:
// 為建立下一個NLComponent對象從str讀取數據,
// 建立component 並返回一個指針。
static NLComponent * readComponent(istream& str);//靜態函數
...
};
NewsLetter::NewsLetter(istream& str)
{
while (str) {
// 把readComponent返回的指針添加到components鏈表的最後,
// "push_back" 一個鏈表的成員函數,用來在鏈表最後進行插入操作。
components.push_back(readComponent(str));//在這調用
}
}在這裡,readComponent根據讀取的數據建立一個新的對象,由於它能夠建立新對象,所以行為與構造函數相似,並且它能根據輸入的不同建立不同類型的對象,因此我們稱它為虛擬構造函數。
虛擬構造函數是指能夠根據輸入給它的數據的不同而建立不同類型的對象。虛擬構造函數在很多場合下都有用處,從磁盤(或者通過網絡連接,或者從磁帶機上)讀取對象信息只是其中的一個應用。
還有一種特殊種類的虛擬構造函數――虛擬拷貝構造函數――也有著廣泛的用途。虛擬拷貝構造函數能返回一個指針,指向調用該函數的對象的新拷貝。因為這種行為特性,虛擬拷貝構造函數的名字一般都是copySelf,cloneSelf或者是象下面這樣就叫做clone。很少會有函數能以這麼直接的方式實現它:
[cpp]
<SPAN style="FONT-SIZE: 18px">class NLComponent {
public:
// 聲明一個虛擬拷貝構造函數
virtual NLComponent * clone() const = 0;
...
};
class TextBlock: public NLComponent {
public:
virtual TextBlock * clone() const // virtual copy
{ return new TextBlock(*this); } // constructor
...
};
class Graphic: public NLComponent {
public:
virtual Graphic * clone() const // virtual copy
{ return new Graphic(*this); } // constructor
...
};</SPAN>
class NLComponent {
public:
// 聲明一個虛擬拷貝構造函數
virtual NLComponent * clone() const = 0;
...
};
class TextBlock: public NLComponent {
public:
virtual TextBlock * clone() const // virtual copy
{ return new TextBlock(*this); } // constructor
...
};
class Graphic: public NLComponent {
public:
virtual Graphic * clone() const // virtual copy
{ return new Graphic(*this); } // constructor
...
};
正如我們看到的,類的虛擬拷貝構造函數只是調用它們真正的拷貝構造函數。因此“拷貝”的含義與真正的拷貝構造函數相同。如果真正的拷貝構造函數只做了簡單的拷貝,那麼虛擬拷貝構造函數也做簡單的拷貝。如果真正的拷貝構造函數做了全面的拷貝,那麼虛擬拷貝構造函數也做全面的拷貝
注意上面的代碼實現采用了較為寬松的虛擬函數返回值類型規則。被派生類重定義的虛函數不用必須與基類的虛擬函數具有一樣的返回值。如果函數的返回類型是一個指向基類的指針(或一個引用),那麼派生類的函數可以返回一個指向基類的派生類的指針(或引用)。這不是C++的類型檢查上的漏洞,它使得有可能聲明象虛擬構造函數這樣的函數。這就是為什麼TextBlock的clone函數能夠返回TextBlock*和Graphic的clone能夠返回Graphic*的原因,即使NLComponent的clone返回值類型為NLComponent*。
在NLComponent中的虛擬拷貝構造函數能讓實現NewLetter的(正常的)拷貝構造函數變得很容易:
[cpp]
<SPAN style="FONT-SIZE: 18px">class NewsLetter {
public:
NewsLetter(const NewsLetter& rhs);//拷貝構造函數
...
private:
list<NLComponent*> components;
};
NewsLetter::NewsLetter(const NewsLetter& rhs)
{
// 遍歷整個rhs鏈表,使用每個元素的虛擬拷貝構造函數
// 把元素拷貝進這個對象的component鏈表。
for (list<NLComponent*>::const_iterator it =
rhs.components.begin();
it != rhs.components.end();
++it) {
// "it" 指向rhs.components的當前元素,調用元素的clone函數,
// 得到該元素的一個拷貝,並把該拷貝放到
// 這個對象的component鏈表的尾端。
components.push_back((*it)->clone());
}
}</SPAN>
class NewsLetter {
public:
NewsLetter(const NewsLetter& rhs);//拷貝構造函數
...
private:
list<NLComponent*> components;
};
NewsLetter::NewsLetter(const NewsLetter& rhs)
{
// 遍歷整個rhs鏈表,使用每個元素的虛擬拷貝構造函數
// 把元素拷貝進這個對象的component鏈表。
for (list<NLComponent*>::const_iterator it =
rhs.components.begin();
it != rhs.components.end();
++it) {
// "it" 指向rhs.components的當前元素,調用元素的clone函數,
// 得到該元素的一個拷貝,並把該拷貝放到
// 這個對象的component鏈表的尾端。
components.push_back((*it)->clone());
}
}
上面的工作就是遍歷被拷貝對象NewsLetter的component鏈表,調用鏈表中的每個元素的虛擬構造函數。
我們在這裡需要一個虛擬構造函數,因為鏈表中包含指向NLComponent對象的指
針,但是我們知道其實每一個指針不是指向TextBlock對象就是指向Graphic對象。無論它指向誰,我們都想進行正確的拷貝操作,虛擬構造函數能夠為我們做到這點。
虛擬化非成員函數
就像構造函數不能成為虛函數一樣,非成員函數也不能成為真正的虛函數。但是,既然一個函數能夠構造出不同類型的新對象是可以理解的,那麼同樣也存在這樣的非成員函數,可以根據其參數的動態類型的不同來展現出不同的特性。
假設你想為TextBlock和Graphic對象實現一個輸出操作符。顯而易見的方法是虛擬化這個輸出操作符。
輸出操作符是 operator<<,這裡為了使得輸出操作符是類的成員函數(支持虛函數),你必須把函數參數 ostream&放在操作符的右邊,作為右參數。
[cpp]
<SPAN style="FONT-SIZE: 18px">class NLComponent {
public:
// 對輸出操作符的不尋常的聲明
virtual ostream& operator<<(ostream& str) const = 0;
...
};
class TextBlock: public NLComponent {
public:
// 虛擬輸出操作符(同樣不尋常)
virtual ostream& operator<<(ostream& str) const;
};
class Graphic: public NLComponent {
public:
// 虛擬輸出操作符 (讓就不尋常)
virtual ostream& operator<<(ostream& str) const;
};
雖然這樣做是可以的,但是我們看看當調用的時候會發生什麼
TextBlock t;
Graphic g;
...
t << cout;
//你需要把cout作為右操作數
// 通過virtual operator<<
//把t打印到cout中。
// 不尋常的語法
g << cout; //通過virtual operator<<
//把g打印到cout中。
//不尋常的語法</SPAN>
class NLComponent {
public:
// 對輸出操作符的不尋常的聲明
virtual ostream& operator<<(ostream& str) const = 0;
...
};
class TextBlock: public NLComponent {
public:
// 虛擬輸出操作符(同樣不尋常)
virtual ostream& operator<<(ostream& str) const;
};
class Graphic: public NLComponent {
public:
// 虛擬輸出操作符 (讓就不尋常)
virtual ostream& operator<<(ostream& str) const;
};
雖然這樣做是可以的,但是我們看看當調用的時候會發生什麼
TextBlock t;
Graphic g;
...
t << cout;
//你需要把cout作為右操作數
// 通過virtual operator<<
//把t打印到cout中。
// 不尋常的語法
g << cout; //通過virtual operator<<
//把g打印到cout中。
//不尋常的語法
這與我們正常熟悉的使用方法相違背,但是為了能夠回到正常的語法上來,我們必須把operator<<移出TextBlock 和 Graphic類,但是如果我們這樣做,就不能再把它聲明為虛擬了。
另一種方法是為打印操作聲明一個虛擬函數(例如print)把它定義在TextBlock 和 Graphic類裡。但是如果這樣,打印TextBlock 和 Graphic對象的語法就與使用operator<<做為輸出操作符的其它類型的對象不一致了,這些解決方法都不很令人滿意。我們想要的是一個稱為operator<<的非成員函數,其具有象print虛擬函數的行為特性。
我們定義operator<< 和print函數,讓前者調用後者!
[cpp]
<SPAN style="FONT-SIZE: 18px">class NLComponent {
public:
virtual ostream& print(ostream& s) const = 0;
...
};
class TextBlock: public NLComponent {
public:
virtual ostream& print(ostream& s) const;
...
};
class Graphic: public NLComponent {
public:
virtual ostream& print(ostream& s) const;
...
};
inline
ostream& operator<<(ostream& s, const NLComponent& c)
{
return c.print(s);
}</SPAN>
class NLComponent {
public:
virtual ostream& print(ostream& s) const = 0;
...
};
class TextBlock: public NLComponent {
public:
virtual ostream& print(ostream& s) const;
...
};
class Graphic: public NLComponent {
public:
virtual ostream& print(ostream& s) const;
...
};
inline
ostream& operator<<(ostream& s, const NLComponent& c)
{
return c.print(s);
}
具有虛擬行為的非成員函數很簡單。你編寫一個虛擬函數來完成工作,然後再寫一個非虛擬函數,它什麼也不做只是調用這個虛擬函數。為了避免這個句法花招引起函數調用開銷,你當然可以內聯這個非虛擬函數。