1.句柄类存储和管理指针是C++一个通用的技术。指针所指对象的类型可以变化,它既可以指向基类类型对象又可以指向派生类类型对象。用户通过句柄类访问继承层次的操作。句柄的用户可以获得动态行为而不需操心指针的管理。
2.句柄类经常需要在不知道对象的确切类型时分配已知对象的副本,为了支持句柄,需要从基类开始,在继承层次的每个类型中增加clone,基类必须将该函数定义为虚函数,以获得动态行为。
3. clone函数
clone函数很重要,要使用句柄类该是不可避免的需要定义clone函数
回想为什么要使用句柄类,一开始,想用multiset来存储某个类的一些对象,这些对象可能存在父子关系,所以不能直接存放对象(子对象转化为父对象将丢失派生部分),那么只能存放指针,然而指针的管理麻烦(这点我倒是不清楚到底有多难)。这样就发明了句柄类,建一个类,该类包含两个指针,一个指针p指向一个希望存储的对象,另一个指针指向一个size_type类型的use整数存放该对象的引用数(引用数的作用即管理指针何时释放)。
回到我们的问题,clone函数干什么用,是这样的,对于一个我们希望存储的对象,我们要把这个对象跟句柄类关联起来才能存储。
那么有两种办法,一,直接把这个对象的地址赋值给句柄类里的p指针(即指向对对象的那个),二,用这个对象复制一个对象,再把这个复制出来的对象拿来跟p关联,
现在我来告诉你,第一种方法不可行:这个原始对象从哪来的我们不确定,如果这个对象的空间管理是由系统管理,那么我们不知道它何时就被释放了,也许能挺到main函数结束,也许在某个函数调用结束,它被释放了我们还能指向它么。或者这个对象用户还希望使用在很多地方,却由于use为0而被我们手工的释放了,这也是灾难。再说,这个对象要是再被拿去初始化另一个句柄类,那么造成的情况是我们有两群,是两群,看清楚了,这两群句柄对象,他们关联到同一个需要存储的对象,却各自有一个计数,相互独立,带来的问题是,该对象的释放如何统一?
所以,我们只能用第二种方法,我是说,我们的需要存储的对象与句柄类对象关联起来,我们用的方法是,用这个对象复制出一个新对象,而把这个新对象与句柄对象关联(重复一下:关联的方法就是把新建对象的地址赋值给句柄对象的p指针)。好了,对于某个我们希望关联到句柄类对象的对象,我们首先要复制它!耐心点,接下来便要讲到clone函数的意义了。在代码中,我们是如何关联的:在我们的句柄对象的构造函数里,传过来的的是需要存储的那群对象的基类引用,我们的构造函数要实现的功能就是根据这个引用复制一个与引用指向的对象相同的对象,问题是,引用他可能指向那一群对象中的任何一个,而接受引用的只是基类对象引用,如何根据基类引用接受的参数去复制派生类对象,你发现没有,在句柄类中是实现不了的!基类引用或指针根本无法调用派生类的方法,再重复,根据一个基类对象引用无法复制一个派生类对象出来。怎么办呢?我们复制不出来,让它自己复制一个给我们不就行了么,所以,关于复制对象的实现,不是在句柄类里,而是在需要被复制的对象所属的类里,那群对象各自有各自所属的类,在它们各自的类里都定义一个clone函数,当然,是虚函数,然后用基类引用接受了那群对象里的某一个,让该对象调用clone,根据多态的特性,它会调用自身所属的类得clone函数,用这个函数,当然可以复制出跟自身一摸一样对象,复制对象的目的就达到了,这就是clone函数的由来。
a) Item_base类是真正用于记录的类
#ifndef ITEM_H
#define ITEM_H
#include<string>
class Item_base{
public:
Item_base(const std::string &book=" ",double sales_price=0.0):
isbn(book),price(sales_price){}
std::string book() const{
return isbn;
}
void set_price(double new_price){
price=new_price;
}
//价格计算函数
virtual double net_price(size_t n)const{
return n*price;
}
virtual Item_base* clone() const{//
return new Item_base(*this);
}
virtual ~Item_base(){}
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,
size_t qty=0,double disc_rate=0.0):
Item_base(book,sales_price),quantity(qty),discount(disc_rate){}
double net_price(size_t n) const =0;
std::pair<size_t,double> discount_policy() const{
return std::make_pair(quantity,discount);
}
protected:
size_t quantity;//可执行折扣策略的购买量
double discount;//折扣率
};
//批量购买折扣类
class Bulk_item : public Disc_item {
public:
Bulk_item (const std::string &book=" ",double sales_price=0.0,
size_t qty=0,double disc_rate=0.0):
Disc_item(book,sales_price,qty,disc_rate){}//注意使用基类的构造函数
double net_price(size_t cnt) const{
if(cnt>quantity)
return cnt*(1-discount)*price;
else
return cnt*price;
}
virtual Item_base* 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,
size_t qty=0,double disc_rate=0.0):
Disc_item(book,sales_price,qty,disc_rate){}
double net_price(size_t cnt) const{
if(cnt<=quantity)
return cnt*(1-discount)*price;
else
return cnt*price-quantity*discount*price;
}
virtual Item_base* clone() const {
return new Lds_item(*this);
}
};
#endif
b) Sales_item类是句柄类,用于包装Item_base并管理Item_base的指针和内存释放
#ifndef SALES_ITEM_H
#define SALES_ITEM_H
#include "Item.h"
class Sales_item{
public:
//默认构造函数,创建未绑定的句柄
Sales_item(): p(0),use(new size_t (1) ) {}
//将创建绑定到Item_base对象副本的句柄
Sales_item(const Item_base &item): p(item.clone()), use(new 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)
return p;
}
const Item_base &operator*(){
if(p)
return *p;
}
void change_price(double new_price){
p->set_price(new_price);
}
private:
Item_base *p;//指向共享Item_base对象的指针
size_t *use;//指向共享的使用计数的指针
//为析构函数和赋值操作符使用的辅助函数
void decr_use(){
if(--*use==0)
delete p;
delete use;
}
};
Sales_item& Sales_item::operator=(const Sales_item& rhs){
++*rhs.use;
decr_use();
p=rhs.p;
use=rhs.use;
return *this;
}
#endif
注意这里multiset容器的使用方法:首先传递给容器一个比较函数,用于对键值进行顺序存储(保证相同ISBN的书放在一起);然后使用upper_bound(*iter)和items.count(*iter)进行了价格的计算。
#ifndef BASKET_H
#define BASKET_H
#include "Sales_item.h"
#include <set>
//compare 函数用于比较对象,以确定Basket的multiset成员中各元素的排列次序
inline bool compare(const Sales_item &lhs,const Sales_item &rhs){
return lhs->book() < rhs->book();//按照ISBN进行排序
}
class Basket {
typedef bool (*Comp)( const Sales_item &lhs, const Sales_item &rhs);
public:
typedef std::multiset<Sales_item,Comp> set_type;//multiset中可以指定比较函数————1
typedef set_type::size_type size_type;
typedef set_type::const_iterator const_iter;
Basket(): items(compare){ };//初始化比较器——————————————————————2
void add_item(const Sales_item &item){
items.insert(item);
}
size_type size(const Sales_item &i) const {
return items.count(i);
}
double total() const;//
private:
std::multiset< Sales_item,Comp > items;
};
double Basket::total()const{
double sum=0.0;
for(const_iter iter=items.begin();iter!=items.end();
iter=items.upper_bound(*iter)){//upper_bound寻找不等于*iter的最右边界
sum+=(*iter)->net_price(items.count(*iter));
}
return sum;
}
#endif
main.cpp
#include<iostream>
#include "Basket.h"
#include "Sales_item.h"
#include "Item.h"
using namespace std;
int main(){
Basket basket;
Sales_item item1(Bulk_item("7-115-14554-7", 99, 2, 0.2));
Sales_item item2(Item_base("7-115-14554-8", 39));
Sales_item item3(Lds_item ("7-115-14554-9", 59, 200, 0.2));
Sales_item item4(Bulk_item("7-115-14554-7", 99, 2, 0.2));
Sales_item item5(Bulk_item("7-115-14554-7", 10, 2, 0.2));
basket.add_item(item1);
cout<<basket.total()<<endl;
basket.add_item(item2);
cout<<basket.total()<<endl;
basket.add_item(item3);
cout<<basket.total()<<endl;
basket.add_item(item4);
cout<<basket.total()<<endl;
basket.add_item(item5);
cout<<basket.total()<<endl;
return 0;
}
objects=main.o
run:$(objects)
g++ -o run $(objects)
main.o: Sales_item.h Item.h Basket.h
.PHONY:
clean
clean:
rm run $(objects)