程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> C++入門知識 >> C++代理類設計(一)

C++代理類設計(一)

編輯:C++入門知識

作用:使設計的容器有能力包含類型不同而彼此相關的對象。

容器通常只能包含一種類型的對象,所以很難再容器中存儲對象本身。存儲指向對象的指針,雖然允許通過繼承來處理類型不同的問題(多態性),但是也增加了內存分配的額外負擔。所以我們通過定義名為代理的對象來解決該問題。代理運行起來和它所代表的對象基本相同,但是允許將整個派生層次壓縮在一個對象類型中。

假設有一個表示不同種類的交通工具的類派生層次:

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數組時,所有這些副本也將被清除。



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