句柄类帮助容器实现对因继承而相关联对象的存储

本文通过构建句柄类解决基类与派生类共存于同一容器时的多态问题,确保不同书籍类别能按各自折扣计算总价。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

文中涉及的代码传送到此处下载

1.问题描述:

        一家综合性的网上书城出售的书籍种类很多,商家为了能够获得更多的利润,对不同的书籍进行不同程度的降价促销,比如对小说类的书籍进行0.5折的优惠,对计算机类的书籍进行0.7折的优惠。现在有一个用户同时选购了很多书籍,其中有几本是小说,几本是计算机类书籍,还有一些是按原价出售的书籍,从程序实现的角度来考虑,我们应该怎么得到这些书籍的总价呢疑问

        为了更清晰的阐述句柄类在这个问题中的应用,我们会对这个问题进行很大程度的简化。从面向对象的角度来说这里需要用到类的继承的思想,首先需要一个Base_class来保存这些书籍共有的特征和公共的操作,比如:书籍的ISBN号,定价,返回书本售价的函数,返回书本ISBN的函数。针对每一类不同的书籍都需要一个Derived_class,它还需要记录针对这一类书籍的折扣力度,以及返回该类书籍打折后价格的函数。

对基类和派生类的定义如下:

books.h文件内容

#include <iostream>
#include <string>
#include <set>

using namespace std;

class Book_base
{
public:
	Book_base(const string& book="", double sales_price=0):
	                  isbn(book),price(sales_price){};
	string get_isbn() const
	{
		return isbn;
	}
	virtual double net_price(int book_count)
	{
		return price*book_count;
	}
	virtual ~Book_base(){}

private:
	string isbn;

protected:
	double price;
};

class Book_detail: public Book_base
{
public:
	double net_price(size_t count) const;

private:
	size_t min_qty;
	double discount;
};

books.cpp文件内容

#include "books.h"

using namespace std;

double Book_detail::net_price(size_t count) const
{
	if(count>=min_qty)
		return count*price*discount;
	else 
		return count*price;

在上述类定义的基础上,我们执行像下面一段代码,想想会发生什么?

main.cpp

#include "books.h"

using namespace std;

int main()
{
	multiset<Book_base> basket;
	Book_base base;
	Book_detail detail;
	basket.insert(base);
	basket.insert(detail);

 return 0; }

当我们调用basket.insert(detail)的时候,就将Book_base的引用绑定到了Book_detail对象上,并将改引用作为实参传递给复制构造函数新建出一个新的对象,并且这个对象只包含Book_detail的基类部分。这时候我们如果调用这个Book_detail对象的net_price函数,得到的就是基类Book_base中定义的这个函数,这就和我们的实际需求不一致了,为了能够更好的解决这个问题,我们就需要用到句柄类。下面我们进一步明确下所遇到的问题:

2.问题分析

根据上述存在的问题可以看出需要解决的几个主要问题是:

a)如何保证同一个容器既可以存储基类对象,也可以存储派生类对象的目的,并且被存储的派生类对象不都会被截断

b)如何以更为优雅的计数式复制控制(复制控制涉及的函数包括复制构造函数,赋值运算符和析构函数)

c)如何保证在计算购物车中书籍总价时候,不同类别的书籍按照不同的折扣程度进行计算

这样看起来形势已经明朗了很多,下面就对症下药,各个击破奋斗

3.解决方案--句柄类闪亮登场

针对问题a),要想将派生类和基类对象共存在一个容器中,同时避免在容器中存储指针(在容器中存储指针想来是极糟糕的),一个方法就是设计一个新类型,并且这个类型需要一个指针,要求它既可以指向基类对象又可以指向派生类对象,所以他必须定义为一个指向基类的指针,在构造函数中利用对应对象的指针初始化它就OK了,这也就是句柄类的构造思想。这样会第一个问题似乎就已经完美解决了。。。。真是的这样吗?等下先把代码写出来看看就知道了。

针对问题b)这个好办,在新建类型内部加上一个引用计数就好了

针对问题c)这个其实也不难只要我们拿到了指向不同类型对象的指针,并将计算每一类书籍价格的函数设置为虚函数,就实现了动态绑定。

下面就是这个句柄类的具体实现代码:

Sales_item.h

#include "books.h"

class Sales_item
{
public:
	Sales_item():p(0),use_count(new size_t(1) ){}
	Sales_item(const Sales_item& s):
	                 p(s.p),use_count(s.use_count)
					 {
						++use_count;
					 }
	Sales_item(const Book_base&);
	~Sales_item(){ use_judge(); };

	const Book_base *operator->() const 
	{ 
		if(p) return p; 
		else
			throw logic_error("unbound Sales_item");
	}

	const Book_base &operator*()const 
	{
		if(p) return *p;
		else
			throw logic_error("Unbound Sale_item");
	}

private:
	Book_base *p;
	size_t *use_count;

	void use_judge()
	{  if(--use_count==0)   { delete p;  delete use_count; } }

};
sales_item.cpp

#include "sales_item.h"

Sales_item& Sales_item::operator = (const Sales_item& rhs)
{
	++*rhs.use_count;
	use_judge();
	p=rhs.p;
	use_count=rhs.use_count;

	return *this;
}

注意类Sales_item中size_t之所以定义成指针是因为operator进行定义的时候,传递的是对Sales_item类型实参的const引用,所以如果直接定义为size_t类型的值就不能改变了,而定义为指针的形式虽然指针不能指向其他类型的size_t类型对象了,但是我们只要对计数值进行改变就OK了。
上述代码似乎已经解决了我们的所有问题,但是仔细观察可以发现,其实还有一个重大漏洞 尴尬,就是利用Sales_item(const Book_base&)进行Sales_item对象构造问题还没有解决,而这个构造函数也是我们必须用到的。我们现在面临的问题是,为了不改变原始的基类或者派生类对象,在这个构造函数中必须使用对他们的const引用作为实参,但是这样一来就不能直接初始化Sales_item中的p指针了。不要慌,我们有办法 大笑,那就是在原来的Book_base类和Book_detail类中分别添加一个clone()函数,这个方法很常用。具体实现如下:

在books.h的Book_base类中加入如下函数定义(这两个函数都用到了默认的复制构造函数)

	virtual Book_base *clone()const 
			  { return new Book_base(*this);}
在books.h的Book_detail类中加入如下函数定义
	virtual Book_detail *clone()const 
	          { return new Book_detail(*this);}
现在我们将Sales_item.h的构造函数Sales_item(const Book_base&)补充完整

	Sales_item(const Book_base& b):
		   p(b.clone()),use_count(new size_t(1)){};
至此我们提出来的三个问题已经完美解决,cheers!!!

4.还有一些小问题

接下来就是考虑怎样将这个对象放到我们指定的容器中,比如multiset,但是multiset的元素需要能够进行<的比较,所以我们需要对<进行重载,multiset接受一个比较函数指针作为对其中元素进行排序的判断依据。所以定义这样一个比较函数:

#include "sales_item.h"

inline bool compare(const Sales_item& lhs,const Sales_item& rhs)
{
	return lhs->get_isbn()<rhs->get_isbn();
}
最后就是购物车类的设计,上面的compare函数定义也放在购物车Basket类定义中

#ifndef BASKET_H
#define BASKET_H

#include "sales_item.h"

inline bool compare(const Sales_item& lhs,const Sales_item& rhs)
{
	return lhs->get_isbn()<rhs->get_isbn();
}


class Basket
{
	typedef bool (*Comp)(const Sales_item& lhs,const Sales_item& rhs);
public:
	typedef multiset<Sales_item,Comp>  set_type;
	typedef set_type::size_type size_type;
	typedef set_type::const_iterator const_iter;

	Basket():items(compare){}

	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:
	multiset<Sales_item,Comp> items;
};

#endif
basket.cpp文件

#include "basket.h"

double Basket::total() const
{
	double sum=0.0;
	
	for(const_iter iter=items.begin();iter!=items.end();iter=items.upper_bound(*iter))
	{
		sum+=(*iter)->net_price(items.count(*iter));
	}

	return sum;
}
最后的main函数

#include "basket.h"

using namespace std;

int main()
{
	Book_base base_book1("Dream",10);
	Book_base book_base2("cute world",18);
	Book_detail book_det1("snow white",11.2,2,0.8);
	Book_detail book_det2("snow white",11.2,2,0.8);
	Book_detail book_det3("Little boat",23.1,1,0.9);
    
	Sales_item item1(base_book1);
	Sales_item item2(book_base2);
	Sales_item item3(book_det1);
	Sales_item item4(book_det2);
	Sales_item item5(book_det3);

	Basket my_basket;
	my_basket.add_item(item1);
	my_basket.add_item(item2);
	my_basket.add_item(item3);
	my_basket.add_item(item4);
	my_basket.add_item(item5);

	double cost = my_basket.total();
	cout<<"Total coat is :"<<cost<<endl;

	return 0;
}
结果截图:


一个小问题:

如果books.h中的Book_base类的net_price()函数不是const函数会造成如下的错误:

basket.cpp(9): error C2662: “Book_base::net_price”: 不能将“this”指针从“const Book_base”转换为“Book_base &”

原因分析:

定位到发生错误的代码是:basket.cpp中的total函数sum+=(*iter)->net_price(items.count(*iter));观察这一行代码可以发现(*iter)调用->返回指向Book_base类的const指针,然后用这个指针调用Book_base类的net_price函数,而指向对象的常量指针是不能调用非const成员函数的。所以发生了类型不能转换的错误。

总结:指向类的常量指针只能调用该类的const类型成员函数,因为非const成员函数可能改变对象的成员,而用常量指针指向类的对象就是为了不改变对象的成员变量,所以会发生错误。


Reference:

C++primer 第四版 chap15

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值