作用:使設計的容器有能力包含類型不同而彼此相關的對象。
容器通常只能包含一種類型的對象,所以很難再容器中存儲對象本身。存儲指向對象的指針,雖然允許通過繼承來處理類型不同的問題(多態性),但是也增加了內存分配的額外負擔。所以我們通過定義名為代理的對象來解決該問題。代理運行起來和它所代表的對象基本相同,但是允許將整個派生層次壓縮在一個對象類型中。
假設有一個表示不同種類的交通工具的類派生層次:
class Vehicle { public: virtual double weight() const = 0; virtual void start() = 0; //... }; class RoadVehicle:public Vehicle{/*...*/}; class AutoVehicle:public Vehicle{/*...*/}; class Aircraft:public Vehicle{/*...*/}; class Helicopter:public Vehicle{/*...*/};
可見Vehicle是一個抽象基類,有兩個純虛函數表示一些共有屬性。下面請看下面這句話為什麼不能達到預期的效果:
Vehicle parking_lot[1000];
表面上看是由於Vehicle是一個抽象基類,因此,只有從類Vehicle派生出來的類才能實例化,類Vehicle本身不會有對象,自然也就不會有對象數組了。
但是,假設我們剔除了類Vehicle中的所有純虛函數,使其對象存在,那又會出現什麼樣的情況呢?看下面的語句:
Automobile x=/*...*/;
parking_lot[num_vehicles++] = x;
把x賦給parking_lot的元素,會把x轉換成一個Vehicle對象,同時會丟失所有在Vehicle類中沒有的成員。該賦值語句還會把這個被剪裁了的對象復制到parking_lot數組中去。這樣,我們只能說parking_lot是Vehicle的集合,而不是所有繼承自Vehicle的對象的集合。
經典解決方案------提供一個間接層
最早的合適的間接層形式就是存儲指針,而不是對象本身:
Vehicle* parking_lot[1000];
然後,就有
Automobile x = /*...*/;
parking_lot[num_vehicles++] = &x;
這種方法解決了迫切的問題,但是也帶來了兩個新問題。
①我們存儲在parking_lot中的是指向x的指針,在上例中是一個局部變量。這樣,一旦變量x沒有了,parking_lot就不知道指向什麼東西了。
我們可以這麼變通一下,放入parking_lot中的值,不是指向原對象的指針,而是指向它們的副本的指針。當我們釋放parking_lot時,也釋放其中所指向的全部對象。
②上述修改雖然不用存儲指向本地對象的指針,但是它也帶來了動態內存管理的負擔。另外,只有當我們知道要放到parking_lot中的對象的靜態類型後,這種方法才起作用。不知道又會怎樣呢?看下面的:
if(p != q)
{
delete parking_lot[p];
parking_lot[p] = parking_lot[q];
}
這樣的話,parking_lot[p]和parking_lot[q]將指向相同的對象,這不是我們想要的。在看下面的行不行:
if(p != q)
{
delete parking_lot[p];
parking_lot[p] = new Vehicle(*parking_lot[q]);
}
這樣我們又回到了前面的問題:沒有Vehicle類型的對象,即使有,也不是我們想要的(是經過剪裁後的對象)。
如何復制編譯時類型未知的對象-------虛復制函數
我們在上面的Vehicle類中加入一個合適的純虛函數:
class Vehicle
{
public:
virtual double weight() const = 0;
virtual void start() = 0;
virtual Vehicle* copy() const = 0;
//...
};
接下來,在每個派生自Vehicle的類中添加一個新的成員函數copy。指導思想就是,如果vp指向某個繼承自Vehicle的不確定類的對象,那麼vp->copy()會獲得一個指針,該指針指向該對象的一個新建的副本。例如:如果Truck繼承自(間接或直接)類Vehicle,則它的copy函數就類似於:
Vehicle* Truck::copy() const
{
return new Truck(*this);
}
當然,處理完一個對象後,需要清除該對象。要做到這一點,就必須確保類Vehicle有一個虛析構函數:
class Vehicle
{
public:
virtual double weight() const = 0;
virtual void start() = 0;
virtual Vehicle* copy() const = 0;
virtual ~Vehicle(){}
//...
};
有了上面的分析,下面我們就來定義代理類:
class VehicleSurrogate { public: VehicleSurrogate(); VehicleSurrogate(const Vehicle&); ~VehicleSurrogate(); VehicleSurrogate(const VehicleSurrogate&); VehicleSurrogate& operator = (const VehicleSurrogate&); private: Vehicle* vp; };
上述代理類有一個以const Vehicle&為參數的構造函數,這樣就能為任意繼承自Vehicle的類的對象創建代理了(多態性,因為這裡是引用參數)。同時,代理類還有一個缺省構造函數,所以我們能夠創建VehicleSurrogate對象的數組。
然而,缺省構造函數也給我們帶來了問題:如果Vehicle是個抽象基類,我們應該如何規定VehicleSurrogate的缺省操作呢?它所指向的對象的類型是什麼呢?不可能是Vehicle,因為根本就沒有Vehicle對象。為了得到一個更好的方法,我們要引入行為類似於零指針的空代理的概念。能夠創建、銷毀和復制這樣的代理,但是進行其他的操作就視為出錯。
下面看各個函數的定義:
VehicleSurrogate::VehicleSurrogate():vp(0){} VehicleSurrogate::VehicleSurrogate(const Vehicle& v):vp(v.copy()){}//非零的檢測室必要的,空代理 VehicleSurrogate::~VehicleSurrogate() { delete vp;//C++標准裡面對一個空指針運用delete也是沒有問題的 } VehicleSurrogate::VehicleSurrogate(const VehicleSurrogate& v):vp(v.vp?v.vp->copy():0){} VehicleSurrogate& VehicleSurrogate::operator=(const VehicleSurrogate& v) { if(this!=&v)//對賦值操作符進行檢測,確保沒有將代理賦值給它自身 { delete vp; vp=(v.vp?v.vp->copy():0);//非零的檢測是必要的,空代理 } return *this; }
下面就很容易定義我們的數組了:
VehicleSurrogate parking_lot[1000];
Automobile x;
parking_lot[num_vehicles++] = x;
最後一條語句就等價於
parking_lot[num_vehicles++] = VehicleSurrogate(x);
這個語句創建了一個關於對象x的副本,並將VehicleSurrogate對象綁定到該副本,然後將這個對象賦值給parking_lot的一個元素。當最後銷毀parking_lot數組時,所有這些副本也將被清除。