關(guān)于類的設(shè)計:代理類
《C++沉思錄》的原話是這樣的
我們怎樣才能設(shè)計一個C++容器,使它有能力包含類型不同而彼此相關(guān)的對象呢?
我們從為什么需要這么一個容器開始討論翠胰。
假設(shè)我們要設(shè)計一個停車場候引,這個停車場就是一個容器。那么停車場需要停各種不同的車輛佑菩,不同的車輛就是不同的類,但他們都是有關(guān)系的(都是交通工具)裁赠。我們知道殿漠,C++標(biāo)準(zhǔn)的容器中儲存的都是相同類型的類,例如數(shù)組佩捞,vector......那么原有的容器就無法滿足我們停車場的需求了绞幌。 所以我們就需要一個能包含類型不同而彼此相關(guān)的對象(車)
下面我們來模擬整個流程
- 在有停車場之前,要先有車一忱。因此莲蜘,首先需要一個抽象基類,命名為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)停車場實際上是不需要知道到底是什么車停進(jìn)來的芬迄,只需要知道它是車问顷。
(2)有車進(jìn)來的時候,我們能跟蹤它禀梳,給一個車位(內(nèi)存)給它杜窄。
(3)車換位置停的時候,我們要知道它換到哪了算途。
(4)當(dāng)車離開的時候塞耕,我們要把車位(內(nèi)存)釋放。
我們通常的做法是用一個指針數(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操作符分配了一塊內(nèi)存(車位)沐兵,返回指向這塊內(nèi)存的指針别垮,大小為Automobile這么大;Automobile(x)是一個復(fù)制構(gòu)造函數(shù)扎谎,返回值是一個和x一樣的類碳想。
這個做法有兩個弊端:1. 增加顯示動態(tài)內(nèi)存管理的負(fù)擔(dān)。2. 我需要確切知道它是什么類型毁靶。 但實際上胧奔,停車場并不需要它到底是哪款車型,只要知道有車進(jìn)來就行了预吆。
如果代碼是這樣的龙填,就很簡潔了
Automobile x = /*.....*/;
parking_lot[num_vehicles++] = x;
不需要顯示的處理內(nèi)存,不需要判斷車的類型拐叉。
如何做到既能避免顯示的處理內(nèi)存分配岩遗,又能保持類在運(yùn)行時綁定的屬性呢?
解決這個問題的關(guān)鍵是要用類來表示概念凤瘦,這在C++中是很常見的宿礁。我總是把這一點當(dāng)作最基本的C++設(shè)計原則。在復(fù)制對象的過程中運(yùn)用這個設(shè)計原則蔬芥,就是定義一個行為和Vehicle對象相似梆靖,而又潛在的表示了所有繼承自Vehicle類的對象的東西,我們把這種類的對象叫做代理(surrogate)
講到這里坝茎,相信大家都應(yīng)該明白涤姊,實際上暇番,停車場需要操作的實際上是車位嗤放,并不是車輛。車位壁酬,是一個跟車輛綁定的東西次酌。在這個例子中,我們可以把車位理解成車輛類的代理舆乔。
無論是第一種變通辦法還是定義代理岳服,我們都需要一個操作,就是復(fù)制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來完成復(fù)制工作吊宋,那么代理類(車位)就比較好寫了:
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)了唾糯,使代碼變得更加簡潔明了怠硼。
最后,當(dāng)然要埋下伏筆啦移怯。什么伏筆呢香璃?
相信細(xì)心的讀者也發(fā)現(xiàn),涉及到代理就離不開復(fù)制芋酌,但是復(fù)制一個類的代價有時候是很大的增显,是我們不愿意的支付的,那我們?nèi)绾伪苊膺@些復(fù)制呢脐帝?希望讀者也能思考思考同云。