程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> 關於C語言 >> 《C++ 沉思錄》閱讀筆記——代理類

《C++ 沉思錄》閱讀筆記——代理類

編輯:關於C語言

Andrew Koenig 和 Barbara Moo 堪稱C++研究領域的”第一神仙眷侶”,看他們的書非常有條理性。這次要解釋的是C++中的另一個常見問題。找出一種優美的控制內存分配的方法來綁定不同子類對象到容器中。多麼復雜的一句話,莫慌,其實很簡單,跟著步伐來看。

首先假設我們要設計一系列交通工具的類,一般來說我們會定義一個交通工具的基類,裡面存放所有交通工具都有的成員和屬性,比如這樣:

class Vehicle {
public:
    virtual double weight() const = 0;
    virtual void start() = 0;
    // ......
};


然後會有一些交通工具繼承關系,比如這樣:

class RoadVehicle : public Vehicle { /* ...... */ };
class AutoVehicle : public RoadVehicle { /* ...... */ };
class Aircraft    : public Vehicle { /* ...... */ };
class Helicopter  : public Aircraft { /* ...... */ };


現在我們要定義一個容器,來保存不同類型的交通工具。

這個要求看起來簡單,但沒有想象中那麼容易。化繁為簡,比如我們用一個數組來保存不同的交通工具,首先我可能會這麼寫:

Vehicle parking_lot[1000];


仔細一想,這麼寫好像不對,為什麼呢?因為 Vehicle 裡面有純虛函數,所以 Vehicle 是個抽象類,抽象類是不會有對象的,所以這麼定義是肯定不行的。一般分析也就到這裡為止了,但繼續想一下,如果我把 Vehicle 中的所有純虛函數去掉,那麼這種定義好像就是OK的,語法上不會有問題,但是有另一個問題,比如下面這樣的賦值:

Helicopter x = /* ...... */
parking_lot[num_vehicles++] = x;


這樣的賦值會導致 Helicopter 對象被轉換成一個Vehicle對象,它將丟失自己的 Helicopter 屬性,這可不是我們想要的,這就好像把一個 double 數轉換成整型放進整形數組裡,丟失了自己的小數部分。

看到這裡,馬上有人會提出,那麼在 parking_lot 中存儲 Vehicle 的指針不就可以了嗎?我們一起來看看:

Vehicle *parking_lot[1000];    // 指針數組


然後我們重復上面的賦值操作:

Helicopter x = /* ...... */
parking_lot[num_vehicles++] = &x;


看起來一切OK,但是有經驗的程序員比如說我,:))一眼就看出這裡很危險,為什麼危險呢?因為存儲指針本身就是一件危險的事情,具體說來,這裡的 x 看起來是一個局部變量,如果 x 被釋放掉了,那麼 parking_lot 數組裡的指針立馬成了懸垂指針,指向什麼內容就不知道了。一個富有責任心的程序員是鐵定不會這麼干的。

那我們是不是就沒折了呢?也不是,既然放指針不行,那麼我復制一下這個對象算了,如下:

Helicopter x = /* ...... */
parking_lot[num_vehicles++] = new Helicopter(x);


雖然浪費了些時間和內存,但是這麼做看起來確實可以,自己分配了內存當然要由自己來釋放,所以我們繼續規定在 delete 這個 parking_lot 的時候,我們也釋放其中所指向的對象。如果這麼干只有自己管理內存這麼一個負擔的話,我想我還能接受,但是這裡有一個不那麼明顯的問題。就是我們放入 parking_lot 中的對象,必須要是已知類型的對象,一說到這裡有的看官就立馬明白了我的意思了,也就是說對於那些編譯時類型未知的對象,這裡就沒辦法保存了,舉個例子,比如我需要在 parking_lot[p] 中放 parking_lot[q] 的對象,該怎麼辦呢?我們並不知道 parking_lot[q] 的對象類型,所以我們沒辦法復制這個對象,同時,我們不能讓 parking_lot 中有兩個指針指向同一個對象,因為我們在刪除這個容器時會把裡面的對象也刪掉,如果有兩個指針指向同一個對象那麼就會刪除兩次。當然,你可以用別的方法來避免,但這還是讓我無法忍受了。

對於編譯時的未知對象,聰明的程序員已經想到辦法解決了。為什麼我們要知道它們是什麼?只要它們自己知道自己是什麼,然後告訴我們就OK了呗!good boy!說明白些,就是我們可以讓繼承自 Vehicle 的類來告訴別人他們到底是什麼,一個簡單的辦法就是在 Vehicle 中定義的 copy 的純虛函數,然後繼承自 Vehicle 的類都設計自己的 copy 函數,用來把自己復制一份返回給調用者,這樣調用者就不用知道這些亂七八糟的交通工具是什麼了。我們來繼續修改代碼:

class Vehicle {
public:
    virtual double weight() const = 0;
    virtual void start() = 0;
    virtual Vehicle *copy() const = 0;
    // ......
};


然後我們修改 Helicopter 類,增加一個 copy 函數:

Vehicle *Helicopter::copy() const
{
    return new Helicopter(*this);
}


這樣我們就再也不需要知道x的類型或者是 parking_lot[q] 的類型了,直接調用 x.copy() 函數或者 parking_lot[q]->copy() 函數就OK了。

parking_lot[num_vehicles++] = x.copy();
parking_lot[p] = parking_lot[q]->copy();


我們完美的解決了上面提到的第二個問題,但程序員從來都是追求完美的,那麼我們有辦法解決這個顯示處理內存分配的問題嗎?這也是程序員幸福的地方,別的領域追求完美是極其困難的,但代碼總能讓我們欣喜。《C++ 沉思錄》裡提到了一個非常深刻的概念——“用類來表示概念”,到底是個什麼意思呢?就是說我們設計類,不光可以是一個具體的事物,同樣,也可以是一個概念,比如,你可以用類來表示人,男人,女人等等,同樣你可以用類來表示家庭,人是具體的,而家庭只是一個概念,家庭裡肯定有有人,所以把控了家庭這個概念,也就把控了人不要跟我抬槓說有些人沒有家庭,舉個例子而已,親!)。

具體表現在代碼上就是我們通過定義一個代理類,來表達這些不同的交通工具,這個代理類應該可以代表不同的交通工具,同時它需要幫助我管理內存,而且需要能夠實例化,因為這樣我就不用再糾結上面那個 Vehicle 是抽象類沒辦法定義容器的問題,所以,這個代理類的作用是讓我能夠定義代理類的容器,同時不需要我來考慮內存的管理問題,而且要支持編譯時類型未知的情況。

代理類只是一個管理交通工具的管理者,它不是一個具體的東西,就跟大明星的經紀人一樣。那看來它必須保存一個明星,也就是得有一個指向交通工具的指針,同時它需要上台面,那麼它需要真實的構造函數,同時它需要能夠放進容器,所以它需要一個默認構造函數:

class VechicleProxy {
public:
    VechicleProxy();
    VechicleProxy(const Vehicle &);
    ~VechicleProxy();
    VechicleProxy(const VechicleProxy &);
    VechicleProxy &operator=(const VechicleProxy &);
private:
    Vehicle *p;
};


上面多加了幾個構造函數和賦值操作符,也不難理解,畢竟是一個真實的類嘛。其中以 const Vehicle& 為參數的復制構造函數就提供了為任意交通工具做代理的能力。一切看起來OK,但是在默認構造函數裡我們能夠為 p 指針賦值什麼呢?好像只能賦為0了。這個零指針也就是說通常說的空代理。那麼讓我們來完成這個代理類的成員函數吧:

VechicleProxy::VechicleProxy(): p(0) { }
VechicleProxy::VechicleProxy(const Vehicle &BigStar): p(BigStar.copy()) {}
VechicleProxy::~VechicleProxy() { delete p; }
VechicleProxy::VechicleProxy(const VechicleProxy &v): p(v.p ? v.p->copy() : 0) {}
VechicleProxy::operator=(const VechicleProxy &v)
{
    if (this != &v)
    {
        delete p;
        p = (v.p ? v.p->copy() : 0);
    }
    return *this;
}


這裡沒有什麼多余的秘密了,仔細點都OK。寫到這裡我們終於可以定義一個完美的 parking_lot 了。

VehicleProxy parking_lot[1000];
Helicopter x;
parking_lot[num_vehicles++] = x;


總結一下:

當我們使用繼承和容器的時候,通常需要處理兩個問題:內存的分配編譯時類型未知對象的綁定使用一個被成為代理類的東西,我們把復雜的繼承層次壓縮到了一起,讓這個類能夠代表所有的子類型,用類來表示概念的武器果然犀利。


本文出自 “菜鳥浮出水” 博客,請務必保留此出處http://rangercyh.blog.51cto.com/1444712/1291958

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