關于類的設計:代理類
《C++沉思錄》的原話是這樣的
我們怎樣才能設計一個C++容器,使它有能力包含類型不同而彼此相關的對象呢?
我們從為什么需要這么一個容器開始討論。
假設我們要設計一個停車場,這個停車場就是一個容器。那么停車場需要停各種不同的車輛,不同的車輛就是不同的類,但他們都是有關系的(都是交通工具)。我們知道,C++標準的容器中儲存的都是相同類型的類,例如數(shù)組,vector......那么原有的容器就無法滿足我們停車場的需求了。 所以我們就需要一個能包含類型不同而彼此相關的對象(車)
下面我們來模擬整個流程
- 在有停車場之前,要先有車。因此,首先需要一個抽象基類,命名為Vehicle。它有一系列的派生實類:Automobile,Truck......
class Vehicle{
public:
virtual double weight() const = 0;
virtual void start() = 0;
// ...
};
class Automobile: public Vehicle {/*...*/};
class Truck: public Vehicle {/*...*/};
......
- 現(xiàn)在我們來模擬停車場(容器)。這個停車場到底需要什么功能呢?
(1)停車場實際上是不需要知道到底是什么車停進來的,只需要知道它是車。
(2)有車進來的時候,我們能跟蹤它,給一個車位(內存)給它。
(3)車換位置停的時候,我們要知道它換到哪了。
(4)當車離開的時候,我們要把車位(內存)釋放。
我們通常的做法是用一個指針數(shù)組
Vehicle* parking_lot[1000];
Automobile x = /*.....*/;
parking_lot[num_vehicles++] = &x;
//num_vehicles means numbers of vehicles
這么做有一個弊端,這個指針是直接指向車本身的。打個比方,假如車開出了停車場,理論上來說,這個指針還會跟著車走,但我的指針是屬于停車場的,出不去,那么車開出去的時候這個指針指向哪里就out of control。
既然這樣,那我們來做第一個變通。我們不讓指針指向車本身,我們指向它的一個副本。
Automobile x = /*.....*/;
parking_lot[num_vehicles++] = new Automobile(x);
我簡單解釋一下第二行等號右邊代碼的意思:new操作符分配了一塊內存(車位),返回指向這塊內存的指針,大小為Automobile這么大;Automobile(x)是一個復制構造函數(shù),返回值是一個和x一樣的類。
這個做法有兩個弊端:1. 增加顯示動態(tài)內存管理的負擔。2. 我需要確切知道它是什么類型。 但實際上,停車場并不需要它到底是哪款車型,只要知道有車進來就行了。
如果代碼是這樣的,就很簡潔了
Automobile x = /*.....*/;
parking_lot[num_vehicles++] = x;
不需要顯示的處理內存,不需要判斷車的類型。
如何做到既能避免顯示的處理內存分配,又能保持類在運行時綁定的屬性呢?
解決這個問題的關鍵是要用類來表示概念,這在C++中是很常見的。我總是把這一點當作最基本的C++設計原則。在復制對象的過程中運用這個設計原則,就是定義一個行為和Vehicle對象相似,而又潛在的表示了所有繼承自Vehicle類的對象的東西,我們把這種類的對象叫做代理(surrogate)
講到這里,相信大家都應該明白,實際上,停車場需要操作的實際上是車位,并不是車輛。車位,是一個跟車輛綁定的東西。在這個例子中,我們可以把車位理解成車輛類的代理。
無論是第一種變通辦法還是定義代理,我們都需要一個操作,就是復制copy(),因此,我們需要更新一下車輛類的定義
class Vehicle{
public:
virtual double weight() const = 0;
virtual void start() = 0;
virtual Vehicle* copy() const = 0;
virtual ~Vehicle() { }
// ...
};
Vehicle* Automobile::copy() const{
return new Automobile(*this);
}
......
有了虛函數(shù)copy來完成復制工作,那么代理類(車位)就比較好寫了:
class VehicleSurrogate{
public:
VehicleSurrogate();
VehicleSurrogate(const Vehicle&);
~VehicleSurrogate();
VehicleSurrogate(const VehicleSurrogate&);
VehicleSurrogate& operate=(const VehicleSurrogate&);
//來自類Vehicle的操作
double weight() const;
void start();
//...
private:
Vehicle* vp;
}
值得注意的是,在代理類(車位)中,我們重載了賦值符‘=’。目的是為了后續(xù)代碼的簡潔。(上述代碼只給出了定義,具體實現(xiàn)比較簡單,需要的私聊)
完成了上述的工作,我們的停車場基本就很容易定義了。
VehicleSurrogate parking_lot[1000];
Automobile x;
parking_lot[num_vehicles++] = x;
最后一行代碼的原型是:
parking_lot[num_vehicles++] = VehicleSurrogate(x);
我們重載的賦值符 ‘=’ 的好處就出現(xiàn)了,使代碼變得更加簡潔明了。
最后,當然要埋下伏筆啦。什么伏筆呢?
相信細心的讀者也發(fā)現(xiàn),涉及到代理就離不開復制,但是復制一個類的代價有時候是很大的,是我們不愿意的支付的,那我們如何避免這些復制呢?希望讀者也能思考思考。