句柄类与继承

前一小节《容器与继承》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;
		else
			return 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;
	else
		return cnt * price;

}

当中基类是不打折的。基类的直接派生类添加了两个成员,各自是购买多少书才会打折的数量(或者是超过多少以后就不打折了的数量,这取决于它的派生类),以及折扣幅度。我们把这个类定义为了虚基类。通过将它的net_price定义为纯虚函数来完毕。定义为虚基类的目的是由于这个类并没有实际的意义,我们不想创建它的对象,而它的派生类,则详细定义了两种不同的打折策略。在基类和派生类中,都定义了clone函数来返回一个自身的副本,在句柄类初始化时,会用得到它们。这里有一点须要注意:普通情况下,虚函数在继承体系中的声明应该是同样的,可是有一种例外情况:基类中的虚函数返回的是指向某一基类(并不一定是这个基类)的指针或者引用,那么派生类中的虚函数能够返回基类虚函数返回的那个基类的派生类(或者是它的指针或者引用)。

然后,我们定义一个句柄类里管理这个继承层次中的基类或者派生类对象:

class Sales_item
{
public:
	//默认构造函数
	//指针置0,不与不论什么对象关联,计数器初始化为1
	Sales_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;
		else
			throw std::logic_error(" unbound Sales_item");
	}
	//重载解引操符
	const Item_base &operator*()const
	{
		if(p)
			//返回Item_base或其派生类的对象
			return *p;
		else
			throw 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;
	//默认构造函数,将比較函数确定为compare
	Basket():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用以跳过全部同样的isbn
	for(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,价格,折扣生效数目,以及折扣率都相等时,才干算作相等,所以这样做非常easy误导类的使用者。这里採取的办法是定义一个比較函数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调用获得。

时间: 2024-10-13 12:01:38

句柄类与继承的相关文章

C++ Primer 学习笔记_72_面向对象编程 --句柄类与继承[续]

面向对象编程 --句柄类与继承[续] 三.句柄的使用 使用Sales_item对象能够更easy地编写书店应用程序.代码将不必管理Item_base对象的指针,但仍然能够获得通过Sales_item对象进行的调用的虚行为. 1.比較两个Sales_item对象 在编写函数计算销售总数之前,须要定义比較Sales_item对象的方法.要用Sales_item作为关联容器的keyword,必须能够比較它们.关联容器默认使用keyword类型的小于操作符,可是假设给Sales_item定义小于操作符,

C++ Primer 学习笔记_71_面向对象编程 --句柄类与继承

面向对象编程 --句柄类与继承 引言: C++中面向对象编程的一个颇具讽刺意味的地方是:不能使用对象支持面向对象编程,相反,必须使用指针或引用. void get_prices(Item_base object, Item_base *pointer, Item_base &reference){ //需要根据指针或引用实际所绑定的类型进行调用 cout<< pointer ->net_price(1)<< endl; cout<< reference.n

C++ Primer 学习笔记_83_模板与泛型编程 --一个泛型句柄类

模板与泛型编程 --一个泛型句柄类 引言: [小心地雷] 这个例子体现了C++相当复杂的语言应用,理解它需要很好地理解继承和模板.在熟悉了这些特性之后再研究这个例子也许会帮助.另一方面,这个例子还能很好地测试你对这些特性的理解程度. 前面示例的Sales_item和Query两个类的使用计数的实现是相同的.这类问题非常适合于泛型编程:可以定义类模板管理指针和进行使用计数.原本不相关的Sales_item类型和 Query类型,可通过使用该模板进行公共的使用计数工作而得以简化.至于是公开还是隐藏下

C++ 句柄类

一.容器与继承 在容器中保存有继承关系的对象时,如果定义成保存基类对象,则派生类将被切割,如果定义成保存派生类对象,则保存基类对象又成问题(基类对象将被强制转换成派生类对象,而派生类中定义的成员未被初始化).     唯一的可行的选择是容器中保存对象的指针.但是需要用户管理对象和指针.C++中一个通用的技术是包装类(cover)或句柄类(handle).用句柄类存储和管理类指针. 句柄类大体上完成两方面的工作: 管理指针,这与智能指针的功能类似. 实现多态,利用动态绑定,是得指针既可以指向基类,

智能指针与句柄类(四)

当我们希望使用容器来保存继承体系中的对象时,容器用于继承体系中的类型会有影响:派生类对象复制到基类对象时,派生类对象将被切掉.那么解决这一问题的方法通常是使用容器保存基类对象的指针,这些指针实际指向的是程序运行时动态分配的派生类对象,用户必须保证在容器析构前调用delete来释放动态分配的对象,如下例: 1 class Base 2 { 3 public: 4 virtual void action() = 0; 5 }; 6 class Derived1 : public Base 7 {..

0722-----C++Primer听课笔记----------句柄类和智能指针

1.再说智能指针 1.1  为什么要用智能指针?对于一个指针,它指向一个动态分配内存的对象,若同时有多个指针指向该对象,那么当我们delete的时候,就会出现delete 一个无效内存的错误,因为该对象已经被delete过了,所以这就造成了错误.针对这一情况,我们想到,new 和 delete 必须是成对出现的,那么联想到类里面,很容易想到这个构造函数和析构函数也是成对出现的,对于每一个对象,初始化的时候会调用构造函数,而销毁的时候必然要调用析构函数.因此我们就可以对 指针 进行封装,将该指针的

从 《Accelerated C++》源码学习句柄类

0  C++中多态的概念 多态是指通过基类的指针或者引用,利用虚函数机制,在运行时确定对象的类型,并且确定程序的编程策略,这是OOP思想的核心之一.多态使得一个对象具有多个对象的属性.class Core作为就算成绩的基类.class Grad为Core的子类,添加了论文(thesis)成绩,Grad最终成绩为论文成绩和基类计算方法得到的成绩中的较小值.这是一个知识点:继承的适用场合就是,子类和基类的功能是相同或者相似,但是子类多了一些扩展. //filename:Core.h #ifndef

智能指针与句柄类(二)

之前文章提到写时复制(copy-on-write)技术,要实现这种功能,针对上文中Handle代码,需要将size_t * use这个抽象出来,封装成一个引用计数类,提供写时复制功能.CUseCount类实现如下: 1 class CUseCount 2 { 3 public: 4 CUseCount(); 5 CUseCount(const CUseCount&); 6 ~CUseCount(); 7 8 bool only()const; //判断引用计数是否为0, 句柄类无法访问priva

智能指针与句柄类(三)

之前文章中实现的写时复制,句柄类中引用计数和T类型指针是分开的,这里换一种方式来处理,将引用计数和T类型指针视为一个整体,当做句柄类模板参数.先对上节中的引用计数进行改造: 1 class CUseCount 2 { 3 public: 4 CUseCount(); 5 CUseCount(const CUseCount&); 6 CUseCount& operator=(const CUseCount&); 7 ~CUseCount(); 8 9 void markUnshare