控制代碼類與繼承

來源:互聯網
上載者:User

前一小節《容器與繼承》http://blog.csdn.net/thefutureisour/article/details/7744790提到過:

對於容器,如果定義為基類類型,那麼則不能通過容器訪問衍生類別新增的成員;如果定義為衍生類別類型,一般不能用它承載基類的對象,即使利用類型轉化強行承載,則基類對象可以訪問沒有意義的衍生類別成員,這樣做是很危險的。對這個問題的解決辦法,是使用容器儲存基類的指標。

在C++中,這類問題有一種通用的解決辦法,稱為控制代碼類。它大體上完成兩方面的工作:

1.管理指標。這與智能指標的功能類似

2.實現多態。利用動態綁定,是得指標既可以指向基類,也可以指向衍生類別。

控制代碼類的設計需要重點考慮兩個因素:

1.如何管理指標

2.是否屏蔽它所管理的基類和衍生類別的介面。這意味著,如果我們充分瞭解繼承成層次的介面,那麼就能直接使用它們;要麼我們將這些介面封裝起來,使用控制代碼類自身的介面。

下面通過一個比較複雜的例子來說明這個問題:

這個例子的大體思路,是使用一個容器(multiset)來類比一個購物車,裡面裝了許多書,有的書是原價銷售的,有的書是打折銷售的,並且打折銷售也分為兩種策略:買的多了才打折;買的少才打折,超出部分原價銷售。最後能夠方便的計算購買各種不同類型的書,在不同的打折條件下一共花了多少錢。

首先,是定義不同打折策略的書籍,它們時控制代碼類要管理的繼承層次:

 

//不使用折扣策略的基類class Item_base{public://建構函式Item_base(const std::string &book = "",double sales_price = 0.0):isbn(book),price(sales_price){ }//返回isbn號std::string book()const {return isbn;}//基類不需要折扣策略virtual double net_price(std::size_t n)const{return n * price;}//解構函式virtual ~Item_base(){};virtual Item_base* clone()const{return new Item_base(*this);}private:std::string isbn;protected:double price;};//儲存折扣率和購買數量的類//它有兩個衍生類別,實現兩種折扣模式class Disc_item:public Item_base{public://預設建構函式Disc_item(const std::string& book = "", double sales_price = 0.0,std::size_t qty = 0,double disc_rate = 0.0):Item_base(book,sales_price),quantity(qty),discount(disc_rate){}//純虛函數:防止使用者建立這個類的對象double net_price(std::size_t)const = 0;//將買多少書與折扣率綁定起來std::pair<std::size_t,double>discount_policy()const{return std::make_pair(quantity,discount);}//受保護的成員供衍生類別繼承protected://實現折扣策略的購買量std::size_t quantity;//折扣率double discount;};//批量購買折扣策略:大於一定的數量才有折扣class Bulk_item:public Disc_item{public://建構函式Bulk_item(const std::string& book = "",double sales_price = 0.0,std::size_t qty = 0,double disc_rate = 0.0):Disc_item(book,sales_price,qty,disc_rate){ }~Bulk_item(){}double net_price(std::size_t)const;Bulk_item* clone()const{return new Bulk_item(*this);}};//批量購買折扣策略:小於一定數量才給折扣,大於的部分照原價處理class Lds_item:public Disc_item{public:Lds_item(const std::string& book = "",double sales_price = 0.0,std::size_t qty = 0,double disc_rate = 0.0):  Disc_item(book,sales_price,qty,disc_rate){ }  double net_price(std::size_t cnt)const  {if(cnt <= quantity)return cnt * (1 - discount) * price;elsereturn cnt * price - quantity * discount * price;  }  Lds_item* clone()const  {return new Lds_item(*this);  }};

double Bulk_item::net_price(std::size_t cnt)const{if(cnt >= quantity)return cnt * (1 - discount) * price;elsereturn cnt * price;}

其中基類是不打折的。基類的直接衍生類別增加了兩個成員,分別是購買多少書才會打折的數量(或者是超過多少以後就不打折了的數量,這取決於它的衍生類別),以及折扣幅度。我們把這個類定義為了虛基類。通過將它的net_price定義為純虛函數來完成。定義為虛基類的目的是因為這個類並沒有實際的意義,我們不想建立它的對象,而它的衍生類別,則具體定義了兩種不同的打折策略。在基類和衍生類別中,都定義了clone函數來返回一個自身的副本,在控制代碼類初始化時,會用得到它們。這裡有一點需要注意:一般情況下,虛函數在繼承體系中的聲明應該是相同的,但是有一種例外情況:基類中的虛函數返回的是指向某一基類(並不一定是這個基類)的指標或者引用,那麼衍生類別中的虛函數可以返回基類虛函數返回的那個基類的衍生類別(或者是它的指標或者引用)。

然後,我們定義一個控制代碼類裡管理這個繼承層次中的基類或者衍生類別對象:

 

class Sales_item{public://預設建構函式//指標置0,不與任何對象關聯,計數器初始化為1Sales_item():p(0),use(new std::size_t(1)){}//接受Item_base對象的建構函式Sales_item(const Item_base &item):p(item.clone()),use(new std::size_t(1)){}//複製控制函數:管理計數器和指標Sales_item(const Sales_item &i):p(i.p),use(i.use){++*use;}//解構函式~Sales_item(){decr_use();}//賦值操作符聲明Sales_item& operator=(const Sales_item&);//重載成員訪問操作符const Item_base *operator->()const{if(p)//返回指向Item_base或其衍生類別的指標return p;elsethrow std::logic_error(" unbound Sales_item");}//重載解引操符const Item_base &operator*()const{if(p)//返回Item_base或其衍生類別的對象return *p;elsethrow std::logic_error(" unbound Sales_item");}private://指向基類的指標,也可以用來指向衍生類別Item_base *p;//指向引用計數std::size_t *use;//解構函式調用這個函數,用來刪除指標void decr_use(){if(--*use == 0){delete p;delete use;}}};

Sales_item& Sales_item::operator=(const Sales_item &rhs){//引用計數+1++*rhs.use;//刪除原來的指標decr_use();//將指標指向右運算元p = rhs.p;//複製右運算元的引用計數use = rhs.use;//返回左運算元的引用return *this;}

控制代碼類有兩個資料成員,分別是指向引用計數的指標和指向基類(或者是其衍生類別的指標)。還重載瞭解引操作符以及箭頭操作符用來訪問繼承層次中的對象。它的建構函式有3個:第一個是預設建構函式,建立一個引用計數為1,指標為空白的對象;第三個是複製建構函式,讓指標指向實參指標所指向的對象,且引用計數+1;第二個建構函式的形參是一個基類的對象的引用,但是實參有可能是基類對象也可能是衍生類別對象,怎麼確定呢?這裡通過基類和衍生類別中clone函數來確定:函數返回的是什麼類型,就是什麼類型。

 

有了前面的鋪墊,我們就可以編寫真正的購物車類了:

 

//關聯容器的對象必須定義<操作inline bool compare(const Sales_item &lhs,const Sales_item &rhs){return lhs->book() < rhs->book();}class Basket{//指向函數的指標typedef bool (*Comp)(const Sales_item&,const Sales_item&);public:typedef std::multiset<Sales_item,Comp> set_type;typedef set_type::size_type size_type;typedef set_type::const_iterator const_iter;//預設建構函式,將比較函數確定為compareBasket():items(compare){}//定義的操作://為容器添加一個對象void add_item(const Sales_item &item){items.insert(item);}//返回購物籃中返回ISBN的記錄數size_type size(const Sales_item &i)const{return items.count(i);}//返回購物籃中所有物品的價格double total()const;private://關聯容器來儲存每一筆交易,通過指向函數的指標Comp指明容器元素的比較std::multiset<Sales_item,Comp> items;};

double Basket::total()const{//儲存運行時的總價錢double sum = 0.0;//upper_bound用以跳過所有相同的isbnfor(const_iter iter = items.begin();iter != items.end();iter= items.upper_bound(*iter)){sum += (*iter)->net_price(items.count(*iter));}return sum;}

購物車是使用multiset實現的,這意味著,相同isbn的書籍是連續存放的。

對於關聯容器,必須支援<操作,但是定義<操作並不好,因為我們的<是通過isbn序號判斷的,而“==”,也改用isbn判斷;可是按常理,只有isbn,價格,折扣生效數目,以及折扣率都相等時,才能算作相等,所以這樣做很容易誤導類的使用者。這裡採取的辦法是定義一個比較函數compare,把它定義成內嵌函式,因為每次向容器插入元素時,都要用到它。而將這個比較函數與容器關聯起來的過程非常的“鬆散”,或者說,耦合度很低:

multiset<Sales_item,Comp> items;意味著我們建立一個名為items的關聯容器,容器的類型是Sales_item的。而且容器通過Comp指向的函數來判斷容器元素的大小。這意味著,在容器的建構函式中,通過將指向函數的指標初始化給不同的函數,就能實現不同的判斷操作。

這個類定義了3個函數,分別用來向購物車中增加新的書籍以及返回某個ISBN書的數量以及計算總的價格。其中total函數值得仔細說明一下:

首先是迴圈的遍曆並不是使用iter++來完成的,而是使用iter = items.upper_bound(*iter)。對於multiset,upper_bound返回的是指向某一個鍵的最後一個元素的下一個位置,這樣就可以一次處理同一本書。當然,這裡的有一個前提,就是對於同一本書,它的折扣策略、折扣率以及達到折扣所滿足的數量是一致的。

其次,迴圈體中的函數寫的非常簡潔:iter解引獲得的是Sales_item對象,利用定義的箭頭操作符可以訪問基類或者衍生類別的net_price函數,這個函數的衍生類別版本需要一個表明有多少本書才打折的實參,這個實參通過調用關聯容器的count調用獲得。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.