《C++沉思录》——类设计核查表代理类句柄类

Posted 松狮MVP

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了《C++沉思录》——类设计核查表代理类句柄类相关的知识,希望对你有一定的参考价值。

      《C++沉思录》集中反映C++的关键思想和编程技术,讲述如何编程,讲述为什么要这么编程,讲述程序设计的原则和方法,讲述如何思考C++编程。


一、类设计核查表

1、你的类需要一个构造函数吗?

2、你的数据成员都是私有的合理吗?

3、你的类需要一个无参的构造函数吗?

             是否需要生成类对象的数组!

4、你的每一个构造函数都初始化所有的数据成员了吗?

             虽然这种说法未必总是正确,但是要积极思考!

5、你的类需要析构函数吗?

6、你的类需要一个虚析构函数吗?

7、你的类需要一个拷贝构造函数吗?

8、你的类需要重载赋值运算符吗?

9、你的操作运算符能正确将对象赋给对象本身吗?

class String
{
public:
	String& operator=(const String&);
private:
	char* data;
};

String& String::operator=(const String& str)
{
	if (&str != this && this->data != NULL)
	{
		//释放旧值
		delete[] data;
		//重新分配
		this->data = new char[strlen(str.data) + 1];
		//复制新值
		strcpy(this->data, str.data);
	}
	return *this;
}

10、你的拷贝构造函数和赋值运算符的参数类型加上const了吗?

X::X(const X& x);
X& X::operator=(const X& x);

                复制、赋值都不会改变原对象,所以 const;使用引用,免去值传递时的拷贝开销。

11、你的类需要重载其他关系运算符(==、!=、<、>等)吗?

12、删除数组时你记住delete [ ]了吗?

13、如果函数有引用类型参数,它们应该是const引用吗?

14、适当地声明成员函数为const了吗?

                const类型对象只能调用“声明为const的成员函数”,而不能调用“非const的成员函数”。

                http://blog.csdn.net/songshimvp1/article/details/50975332


二、代理类(surrogate)

       代理类的作用:允许在一个容器中储存类型不同但是相互关联的对象。它允许将整个派生层次压缩在一个对象类型中。代理类是句柄类中最简单的一种。

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

class Aircraft :public Vehicle
{

};
class Helicopter :public Aircraft
{

};
        对于我们要创建的Vehicle对象数组:Vehicle parking_lot [100];(1)Vehicle是一个抽象类,不能实例化对象;(2)即使有vehicle对象,把一个派生类对象赋给某个数组元素,还是会把派生类对象转换成一个vehicle对象,裁减掉所有在Vechile类中没有的成员,最终得到的并不是继承自vehicle的对象集合,仍旧是vehicle对象的集合。


第一种解决方法:这样使用数组:Vehicle* paking_lot [100]——存储指针,而不是储存对象本身。

        这种方法会带来指针异常和动态内存管理的负担。另外我们还必须实现就知道要给某数组元素赋什么派生类型(静态)。

第二种解决方法:

class Vehicle
{
public:
	virtual double weight() const = 0;
	virtual void start() = 0;
	virtual Vehicle* copy() const = 0;    //虚复制————根据需要复制对象,在运行时绑定属性
	virtual ~Vehicle() = 0;               //虚析构
};

class Truck :public Vehicle
{
	double weight() const
	{

	}
	void start()
	{

	}
	Vehicle* copy() const
	{
		return new Truck(*this);    //使用vp->copy()会得到一个指针,该指针指向该对象的一个新建的副本。
	}
};

class VehicleSurrogate    //定义Vehicle的代理类——行为和Vehicle相似
{
public:
	VehicleSurrogate();   //用来创建代理对象数组
	VehicleSurrogate(const Vehicle&);   //用来创建派生类对象的代理
	~VehicleSurrogate();

	VehicleSurrogate(const VehicleSurrogate&);
	VehicleSurrogate& operator=(const VehicleSurrogate&);

	//Vehicle的其他操作
	double weight();
	void start();
private:
	Vehicle* vP;    //关联
};
VehicleSurrogate::VehicleSurrogate() :vP(0)
{

}
VehicleSurrogate::VehicleSurrogate(const Vehicle& v) :vP(v.copy())
{

}
VehicleSurrogate::~VehicleSurrogate()
{
	delete vP;
}

VehicleSurrogate::VehicleSurrogate(const VehicleSurrogate& v) :vP(v.vP ? v.vP->copy() : 0)  //(1)注意对v.vP的非0检测;(2)虚调用v.vP->copy();
{

}
VehicleSurrogate&  VehicleSurrogate::operator=(const VehicleSurrogate& v)
{
	if (this != &v)
	{
		delete vP;
		vP = (v.vP ? v.vP->copy() : 0);  //(1)注意对v.vP的非0检测;(2)虚调用v.vP->copy();
	}
	return *this;
}

double VehicleSurrogate::weight()
{
	if (vP == 0)
	{
		throw "empty VehicleSurrogate.Weight()";
	}
	return vP->weight();
}
void VehicleSurrogate::start()
{
	if (vP == 0)
	{
		throw "empty VehicleSurrogate.start()";
	}
	return vP->start();
}
然后,我们这么使用数组:——通过在容器中使用代理对象,而不是对象本身!

        int index = 0;
	VehicleSurrogate parking_lot[1000];
	Truck vT;
	parking_lot[++index] = VehicleSurrogate(vT);


三、句柄类(handle)

(1)句柄要解决的问题:

        上述使用代理类,我们看见了需要复制代理对象,复制消耗会很大,或者是不能轻易被复制(文件)。句柄类就是为了解决:句柄类允许在保持代理的多态行为的同时,还可以避免进行不必要的复制!

(2)句柄的简单实现——引用计数型句柄

        使用句柄的原因之一就是为了避免不必要的对象复制,也就是得允许多个句柄绑定到同一个对象上,也就需要知道有多少个句柄绑定在同一个对象上,以便确定应当在何时删除对象。——使用引用计数来实现

#include <iostream>
#include <string>
#include <vector>
using namespace std;

class Point
{
public:
	Point() :xVal(0), yVal(0) { }
	Point(int x,int y) :xVal(x), yVal(y) { }
	int getX() const 
	{ 
		return xVal; 
	}
	int getY() const 
	{ 
		return yVal; 
	}
	Point& x(int x)
	{
		xVal = x;
		return *this;
	}
	Point& y(int y)
	{
		yVal = y;
		return *this;
	}

private:
	int xVal;
	int yVal;
};

class UPoint  //该类纯粹是为了在添加引用计数后不重写Point而设计
{
	friend class Handle;
private:
	UPoint() :u(1) {}
	UPoint(int x, int y):p(x, y),u(1)
	{

	}
	UPoint(const Point& p0):p(p0),u(1)
	{

	}
private:
	Point p;  
	int u;      //引用计数——不能把引用计数放在句柄类
};

class Handle
{
public:
	Handle();
	Handle(int, int);
	Handle(const Point&);

	Handle(const Handle&);
	Handle& operator=(const Handle&);
	~Handle();

	int getX() const;
	Handle& x(int);

	int getY() const;
	Handle& y(int);
private:
	UPoint* uP;  //句柄类关联UPoint类
};

Handle::Handle() : uP(new UPoint)
{

}
Handle::Handle(int x, int y)  :uP(new UPoint(x, y))
{

}
Handle::Handle(const Point& p) : uP(new UPoint(p))
{

}

Handle::Handle(const Handle& h) : uP(h.uP)   //拷贝构造,这样原先的句柄和其副本都指向同一个UPoint对象
{
	++uP->u;     //引用计数加1,避免了复制Point
}

Handle& Handle::operator=(const Handle& h)  //赋值运算符,左侧的句柄在赋值后将指向另外一个对象
{
	//首先递增右侧句柄指向对象的引用计数
	++h.uP->u;
	//然后递减左侧句柄所指向对象的引用计数
	if (--(this->uP->u) == 0)
	{
		delete uP;
	}
	//赋值
	this->uP = h.uP;
	return *this;
}

Handle::~Handle()
{
	if (--uP->u == 0)    //递减引用计数,如果引用计数减为0,就删除Upoint对象
	{
		delete uP;
	}
}

//以下“存数据成员”方式是“值语义”,也就是所改动的那个UPoint对象不影响其他的UPoint对象
//{
//	Handle h1(1, 2);
//	Handle h2 = h1;
//	h2.x(3);
//	int n = h1.x();    //输出1
//}
//这就需要保证所改动的那个UPoint对象不能同时被其他任何Handle所引用
Handle& Handle::x(int x0)
{
	if (this->uP->u != 1)         //?没理解?——写时复制,只有在绝对必要时才复制,避免不必要的复制
	{
		--this->uP->u;
		this->uP = new UPoint(this->uP->p);
	}

	this->uP->p.x(x0);
	return *this;
}
Handle& Handle::y(int y0)
{
	if (this->uP->u != 1)
	{
		--this->uP->u;
		this->uP = new UPoint(uP->p);
	}

	this->uP->p.y(y0);
	return *this;
}

//如果是希望赋值以后,改变一个就影响到另外一个,
//{
//	Handle h1(1, 2);
//	Handle h2 = h1;
//	h2.x(3);
//	int n = h1.x();    //输出3
//}
//这就需要保证h1和h2绑定到同一个对象上
Handle& Handle::x(int x0)
{
	uP->p.x(x0);
	return *this;
}
Handle& Handle::y(int y0)
{
	uP->p.y(y0);
	return *this;
}

int Handle::getX() const
{
	return uP->p.getX();
}
int Handle::getY() const
{
	return uP->p.getY();
}

(3)句柄——分离引用计数(成员)


#include <iostream>
#include <string>
#include <vector>
using namespace std;

class Point
{
public:
	Point() :xVal(0), yVal(0) { }
	Point(int x,int y) :xVal(x), yVal(y) { }
	int getX() const 
	{ 
		return xVal; 
	}
	int getY() const 
	{ 
		return yVal; 
	}
	Point& x(int x)
	{
		xVal = x;
		return *this;
	}
	Point& y(int y)	
	{
		yVal = y;
		return *this;
	}

private:
	int xVal;
	int yVal;
};

class Handle
{
public:
	Handle();
	Handle(int, int);
	Handle(const Point&);

	Handle(const Handle&);
	Handle& operator=(const Handle&);
	~Handle();
private:
	Point* p;  //句柄类关联Point类,而不是UPoint类,这样的话我们不仅可以将Handle绑定到一个Point,还可以绑定到继承自Point类的对象上
	int* u;
};

Handle::Handle() : u(new int(1)) , p(new Point)
{

}
Handle::Handle(int x, int y)  : u(new int(1)), p(new Point(x, y))
{

}
Handle::Handle(const Point& p) : u(new int(1)), p(new Point(p))
{

}

Handle::Handle(const Handle& h) : u(h.u) ,p(h.p)   //拷贝构造,这样原先的句柄和其副本都指向同一个UPoint对象
{
	++*u;     //引用计数加1,避免了复制Point
}

Handle& Handle::operator=(const Handle& h)  //赋值运算符,左侧的句柄在赋值后将指向另外一个对象
{
	//首先递增右侧句柄指向对象的引用计数
	++*h.u;
	//然后递减左侧句柄所指向对象的引用计数
	if (--*u == 0)
	{
		delete u;
		delete p;
	}
	//赋值
	this->p = h.p;
	this->u = h.u;
	return *this;
}

Handle::~Handle()
{
	if (--*u == 0)    //递减引用计数,如果引用计数减为0,就删除Upoint对象	
	{
		delete u;
		delete p;
	}
}

(4)句柄——分离引用计数(抽象成一个单独的类)

          UseCount类可以在不了解其使用者任何信息的情况下与之合为一体,可以把这个类当成句柄实现的一部分,与各种不同的数据结构协同工作。

#include <iostream>
#include <string>
#include <vector>
using namespace std;

class Point
{
public:
	Point() :xVal(0), yVal(0) { }
	Point(int x,int y) :xVal(x), yVal(y) { }
	int getX() const 
	{ 
		return xVal; 
	}
	int getY() const 
	{ 
		return yVal; 
	}
	Point& x(int x)
	{
		xVal = x;
		return *this;
	}
	Point& y(int y)	
	{
		yVal = y;
		return *this;
	}

private:
	int xVal;
	int yVal;
};

class UseCount    //对引用计数的抽象
{
public:
	UseCount() :p(new int(1))
	{

	}
	UseCount(const UseCount& u) : p(u.p)
	{
		++*p;
	}	
	~UseCount()
	{
		if (--*p == 0)
			delete p;
	}

	bool only()          //描述该UseCount对象是否是唯一指向它的计数器的对象
	{
		return *p == 1;
	}

	bool reattach(const UseCount& u)
	{
		++*u.p;
		if (--*p == 0)
		{
			delete p;
			p = u.p;
			return true;
		}
		p = u.p;
		return false;
	}

	bool makeOnly()
	{
		if (*p == 1)
		{
			return false;
		}
		--*p;
		p = new int(1);
		return true;
	}
private:
	int *p;	
};

class Handle
{
public:
	Handle();
	Handle(int, int);
	Handle(const Point&);

	Handle(const Handle&);
	Handle& operator=(const Handle&);
	~Handle();

	int getX() const;
	Handle& x(int);

	int getY() const;
	Handle& y(int);
private:
	Point* p;  //句柄类关联Point类,而不是UPoint类,这样的话我们不仅可以将Handle绑定到一个Point,还可以绑定到继承自Point类的对象上
	
	UseCount u;
};

Handle::Handle() : p(new Point)
{

}
Handle::Handle(int x, int y)  : p(new Point(x, y))
{

}
Handle::Handle(const Point& p) : p(new Point(p))
{

}

Handle::Handle(const Handle& h) : u(h.u) ,p(h.p)   //拷贝构造,这样原先的句柄和其副本都指向同一个UPoint对象
{
	
}

Handle& Handle::operator=(const Handle& h)  
{
	if (u.reattach(h.u))
	{
		delete p;
	}
	this->p = h.p;
	return *this;
}

Handle::~Handle()
{
	if (u.only())
	{
		delete p;
	}
}

Handle& Handle::x(int x0)
{
	if (u.makeOnly())
	{
		p = new Point(*p);
	}
	p->x(x0);
	return *this;
}
Handle& Handle::y(int y0)
{
	if (u.makeOnly())
	{
		p = new Point(*p);
	}
	p->y(y0);
	return *this;
}

int Handle::getX() const
{
	return p->getX();
}
int Handle::getY() const
{
	return p->getY();
}

以上是关于《C++沉思录》——类设计核查表代理类句柄类的主要内容,如果未能解决你的问题,请参考以下文章

设计模式之美(c++)-笔记-48-代理模式

设计模式之美(c++)-笔记-48-代理模式

面向对象程序设计中啥是类类有几种特性

Java二十三设计模式之------中介者模式

C++学习书单

构建之法读后感