句柄类, 来自于C++ 沉思录的概念
百度百科:
句柄(handle)是C++程序设计中经常提及的一个术语,它并不是一种具体的、固定不变的数据类型或实体,而是代表了程序设计中的一个广义的概念,句柄一般是指获取另一个对象的方法 —— 一个广义的指针,它的具体形式可能是一个整数、一个对象或就是一个真实的指针,而它的目的就是建立起与被访问对象之间的唯一的联系
句柄(handle)类,它允许在 保持代理的多态行为 的同时,还可以避免进行不必要的复制。
一、问题的提出
通常参数和返回值是通过复制传递的;
只要几个指针指向同一个对象,就必须考虑在什么时候删除这个对象;
在保持多态性的前提下避免复制对象;
handle类的对象 通常被绑定到 它们所控制的类的对象上,这些 handle的行为类似指针,所以人们有时也叫它们智能指针;
将handle直接绑定到对象p上,则我们的handle最好与p的内存分配和释放无关;
#include <iostream> using namespace std; class Point{ public: Point():xval(0),yval(0) { } //缺省/无参构造 Point(int x, int y): xval(x),yval(y) { } //有参构造 int x() const { return xval; } //返回xval int y() const { return yval; } //返回yval Point& x(int xv) { xval = xv; return *this; } //更改xval值返回对象 Point& y(int yv) { yval = yv; return *this; } //更改yval值返回对象 private: int xval, yval; }; /* handle可以控制对Point副本的操作, 是一种只包含单个Point对象的容器 */ class Handle{ public: Handle(int x, int y){ p = new Point(x,y); } Handle(Point& p){ this->p = new Point(p.x(),p.y()); //创建一个p的副本 } Point* operator->(){ return p; }; //Point* add = h.operator->(); ~Handle(){ delete p; } private: Point* p; }; int main() { Point p(1,2); /* 将handle直接绑定到对象p上,则我们的handle与p的内存分配和释放无关 */ Handle h0(123,456); //h0绑定到坐标123,456的Point Handle h(p); //h创建一个p的副本,并将handle绑定到该副本,控制对副本的操作 cout << h->x() << endl; //使用 operator-> 将Handle的所有操作转发给相应的Point操作来执行 cout << h.operator->()->y() << endl; //h.operator->() 返回值Point* cout << h0->x() << endl; cout << h0.operator->()->y() << endl; return 0; }
二、简单的实现,必须明确地选择让我们的handle类支持哪些Point操作,避免使用operator->隐藏Point对象的真实地址
通过无参构造,x,y参数构造,Point对象引用,Handle对象引用复制 (拷贝)构造,通过Handle对象对另一个Handle对象的赋值,使得多个Handle对象指向同一个Point对象,而不复制Point对象,Point对象在没有Handle对象指向的时候释放
class Point{ public: Point():xval(0),yval(0) { } Point(int x, int y): xval(x),yval(y) { } int x() const { return xval; } int y() const { return yval; } Point& x(int xv) { xval = xv; return *this; } Point& y(int yv) { yval = yv; return *this; } private: int xval, yval; }; //为了避免不必要的对象复制,得允许多个句柄绑定到单个对象上 //我们必须定义一个新的类来容纳一个引用计数和一个Point对象,称之为UPoint class UPoint{//所有成员都是私有的 friend class Handle; Point p; int u; UPoint():u(1) { } UPoint(int x, int y): p(x,y), u(1) { } UPoint(const Point& p0):p(p0), u(1) { } }; class Handle{ public: Handle(); Handle(int,int); Handle(const Point&); Handle(const Handle&); Handle& operator = (const Handle&); ~Handle(); int x()const; Handle& x(int); int y()const; Handle& y(int); private: UPoint * up;//添加的 }; 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() { if(--up->u == 0) delete up; } Handle::Handle(const Handle& h):up(h.up) { ++up->u; } Handle& Handle::operator=(const Handle& h) { ++h.up->u; if(--up->u == 0) delete up; up = h.up; return *this; } int Handle::x() const { return up->p.x(); } int Handle::y() const { return up->p.y(); } //指针语义, 通过任一指向Point对象的Handle对象,可以修改Point对象的值 Handle& Handle::x(int x0) { up->p.x(x0); return *this; } Handle& Handle::y(int y0) { up->p.y(y0); return *this; }
代码测试
#include <iostream> using namespace std; class Point{ public: Point():xval(0),yval(0) { } Point(int x, int y): xval(x),yval(y) { } int x() const { return xval; } int y() const { return yval; } Point& x(int xv) { xval = xv; return *this; } Point& y(int yv) { yval = yv; return *this; } private: int xval, yval; }; //为了避免不必要的对象复制,得允许多个句柄绑定到单个对象上 //我们必须定义一个新的类来容纳一个引用计数和一个Point对象,称之为UPoint class UPoint{ /* //所有成员都是私有的 */ friend class Handle; Point p; int u; UPoint():u(1) { } UPoint(int x, int y): p(x,y), u(1) { } UPoint(const Point& p0):p(p0), u(1) { } }; class Handle{ /*private: class UPoint{ //可以设置为内部类,所有成员Handle均可见,外不可见 public: int u; Point p; UPoint():u(1) { } UPoint(int x, int y): p(x,y), u(1) { } UPoint(const Point& p0):p(p0), u(1) { } }; */ public: Handle(); Handle(int,int); Handle(const Point&); Handle(const Handle&); Handle& operator = (const Handle&); ~Handle(); int x()const; Handle& x(int); int y()const; Handle& y(int); int UPoint_u(); //测试用 private: UPoint * up; }; 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() { if(--up->u == 0) //原来的Point对象一旦没有Handle对象指, 即释放 delete up; //cout << "~Handle()\n"; //测试用 } Handle::Handle(const Handle& h):up(h.up) { ++up->u; } Handle& Handle::operator=(const Handle& h) { ++h.up->u; // h指向的Point对象的指针数增一 if(--up->u == 0) //Handle对象原来指向的Point对象的指针数减一 delete up; //原来的Point对象一旦没有Handle对象指, 即释放 up = h.up; //Handle对象的指针, 指向h指向的Point对象 return *this; } int Handle::x() const { return up->p.x(); } int Handle::y() const { return up->p.y(); } //指针语义, 通过任一指向Point对象的Handle对象,可以修改Point对象的值 Handle& Handle::x(int x0) { up->p.x(x0); return *this; } Handle& Handle::y(int y0) { up->p.y(y0); return *this; } //测试用 int Handle::UPoint_u(){ return up->u; } //多个Handle对象, 指向同一个Point对象p, 一样的坐标不一定是同一个对象 int main() { Handle h1(123,456); Handle h2 = h1; Handle h3; h3 = h2; h3.y(789); cout << h3.UPoint_u() << endl; cout << h2.y() << endl; Handle h4(123,789); cout << h4.UPoint_u() << endl; return 0; }
三、实现的改进,引用计数与Point对象本身分离,在句柄中额外加上一些信息 int * u
Handle类数据成员, 使用Point* ,不仅能够将一个Handle绑定到一个Point,还能将其绑定到一个继承自Point类的对象
Handle类,参数为 Point引用 的复制构造函数,复制一个副本,即一个Point 对象副本,即参数的副本,该副本为动态构造
#include <iostream> using namespace std; class Point{ public: Point():xval(0),yval(0) { } Point(int x, int y): xval(x),yval(y) { } int x() const { return xval; } int y() const { return yval; } Point& x(int xv) { xval = xv; return *this; } Point& y(int yv) { yval = yv; return *this; } private: int xval, yval; }; class Handle{ public: Handle(); Handle(int,int); Handle(const Point&); Handle(const Handle&); Handle& operator = (const Handle&); ~Handle(); int x()const; Handle& x(int); int y()const; Handle& y(int); int handle_u(){ return *u; } //测试用 private: Point* p; 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& p0):u(new int(1)), p(new Point(p0)) { } Handle::~Handle() { if(--*u == 0){ delete u; delete p; } } Handle::Handle(const Handle& h):u(h.u), p(h.p) { ++*u; } Handle& Handle::operator=(const Handle& h) { ++*h.u; // h指向的Point对象的指针数增一 if(--*u == 0){ //Handle对象原来指向的Point对象的指针数减一 delete u; delete p; //原来的Point对象一旦没有Handle对象指, 即释放 } u = h.u; p = h.p; return *this; } int Handle::x() const { return p->x(); } int Handle::y() const { return p->y(); } //指针语义, 通过任一指向Point对象的Handle对象,可以修改Point对象的值 Handle& Handle::x(int x0) { p->x(x0); return *this; } Handle& Handle::y(int y0) { p->y(y0); return *this; } //多个Handle对象, 指向同一个Point对象 int main() { Handle h1(123,456); Handle h2 = h1; Handle h3; h3 = h2; h3.y(789); cout << h3.handle_u() << endl; cout << h2.y() << endl; Handle h4(123,789); cout << h4.handle_u() << endl; return 0; }
四、总结
句柄通过绑定类的指针与绑定的类对象或其子类对象进行绑定,之后,通过句柄的计数器,使多个句柄与之进行绑定,只要有一个句柄指向被绑定对象,该对象都不会释放,不因其他绑定句柄改变指向或销毁而释放,保证了被绑定对象的安全,也保证了与之绑定的句柄都有具体的指向; 绑定对象为动态构造 ,句柄的成员函数包装绑定类对象的类成员函数,使得句柄对象控制绑定对象的行为,与绑定对象行为相似
句柄的实质,就是为了指针的安全
句柄的 参数为类对象(非句柄类对象)的 复制构造函数,会复制类对象,然后绑定复制的类对象
绑定同一个类对象的句柄,其计数器空间、绑定的类对象的空间是一样的
句柄对象析构时,计数器值会减一,句柄对象的成员变量会释放,只要计数器值不为0,句柄对象成员变量指向的空间不会释放(包括计数器空间,绑定类对象空间)
//测试代码 Handle h1(123,456); Handle h2(123,456); //与h1绑定的不是同一个对象 { Point p(8,9); // p出了作用域销毁 Handle h3(p); //用p的引用创建绑定对象, h3出了作用域销毁 h2 = h3; //改变h2指向 cout << h2.handle_u() << endl; //h2,h3绑定同一个对象 } cout << h2.handle_u() << endl; //大括号作用域里绑定的对象依然存在 cout << h2.x() << "," << h2.y() << endl;
五、句柄计数器的抽象