最近工作中用到了句柄类,特意了解了一下,后来发现所谓的句柄其实和智能指针就是一回事。为了能够更好的理解句柄类,建议大家先了解c++中类、类的继承、多态等概念,不然很容易懵。
什么是句柄类
句柄(handler)之所以翻译成柄,就是用一个类来撬动很多类。类似于从一大堆数中,只要拿着一个数(基类),后面跟着很多数(继承类)也都顺带被拿起来了。
句柄的实现主要实现了三个作用:
- 支持面对对象编程,实现了多态性质
- 减少头文件的编译依赖关系,让文件间的编译更加独立
- 有智能指针的特性,不需要程序员手动释放内存
为什么要引入句柄类
先来看一个例子,此处参考神奇爱哥的博文。
#include<iostream> #include<vector> using namespace std; class A { public: A(){} ~A(){} virtual void func() { cout<<"A"<<endl;} }; class B: public A { public: B(){} ~B(){} void func() { cout<<"B"<<endl;} }; int main() { A a; B b; vector<A> vec; vec.push_back(a); vec.push_back(b); vec[0].func(); vec[1].func(); }
最后的输出是:
因为对象b的拷贝在vector中时被强制转换成基类了。
要在一个容器中放基类及其继承类的时候总会遇到这个问题,因为vector的类型只能被声明为某一个类(假设为基类),那么继承类的信息就丢失了。
首先可以想到用指针来实现多态,例如vector<A*>。但是这样必须要程序员来接管内存。
B *b = new B;
vec.push_back(b);
这时加入vec的生命周期结束了,vec不会主动释放b所占用的内存,如果不手动deleteb,那么就会造成内存泄漏。
而句柄类可以解决这个问题。
#include<iostream> #include<vector> using namespace std; class A { public: A(){} ~A(){} virtual void func() const { cout<<"A"<<endl;} virtual A* clone() const //为了实现句柄类新增的函数 { return new A(*this);} }; class B: public A { public: B(){} ~B(){} void func() const { cout<<"B"<<endl;} virtual B* clone() const //为了实现句柄类新增的函数 { return new B(*this);} }; class sample { public: sample(): p(0),use(1){} sample(const A& a):p(a.clone()){use++;} //引用构造 sample(const sample& i):p(i.p),use(i.use){use++;} //引用构造 ~sample() {decr_use();} sample& operator=(const sample &i) { use++; decr_use(); p = i.p; use = i.use; return (*this); } const A* operator->() const {return p;} const A& operator*() const {return *p;} private: A* p; size_t use; void decr_use(){if(--use==0) delete p;} }; int main() { vector<sample> vec; //sample类看起来是对象,但是其实用法和指针一样。 A a; B b; sample s1(a); //将a拷贝构造了一个新对象,并让s1指向这个新的对象。此时s1已经与a无关了。 sample s2(b); //将b拷贝构造了一个新对象,并让s2指向这个新的对象。此时s2已经与b无关了。 vec.push_back(s1); vec.push_back(s2); vec[0]->func(); vec[1]->func(); }
此时运行结果为:
因此句柄类方便得实现了多态,并且和智能指针一样可以自动管理内存。
上面的代码需要注意为什么克隆函数里要用
virtual A* clone() const {return new A(*this);}
而不是直接virtual A* clones() const(return this;}
这是为了避免这样一种情况:
B b;
sample sam(b);
vec.push_back(sam);
当b的生命周期结束,而vec的生命周期未结束时,调用(*iter)->func就会出错。
句柄类的作用
从上面的例子中可以看出,句柄类sample的实现和它所管理的类A的实现时完全独立的。因此即使更改了A的功能,句柄类也依然可以保持不变。
句柄类本质是指针!!
在使用句柄类需要特别注意的一点是,操作句柄类时本质就是在操作指针,有时解引用了非const的函数可能就会直接改变其真正指向的对象。
来看一个例子。
#include<iostream> #include<vector> using namespace std; class A { public: A(){numA = 0;} ~A(){} virtual void func() const { cout<<"A"<<endl;} virtual A* clone() const //为了实现句柄类新增的函数 { return new A(*this);} void setNum(int n) //增加了一个设置num属性的函数 { numA = n;} int getNum() const { return numA;} private: int numA; }; class B: public A { public: B(){numB = 0;} ~B(){} void func() const { cout<<"B"<<endl;} virtual B* clone() const //为了实现句柄类新增的函数 { return new B(*this);} void setNum(int n) //增加了一个设置num属性的函数 { numB = n;} int getNum() const { return numB;} private: int numB; }; class sample { public: sample(): p(0),use(1){} sample(const A& a):p(a.clone()){use++;} //引用构造 sample(const sample& i):p(i.p),use(i.use){use++;} //引用构造 ~sample() {decr_use();} sample& operator=(const sample &i) { use++; decr_use(); p = i.p; use = i.use; return (*this); } A* operator->() const {return p;} A& operator*() const {return *p;} private: A* p; size_t use; void decr_use(){if(--use==0) delete p;} }; void changeA(sample sam) { sam->setNum(6); } int main() { A a; sample s1(a); cout<<"s1中num的值: "<<s1->getNum()<<endl; cout<<"a的num值: "<<a.getNum()<<endl; changeA(s1); cout<<"更改后s1中num的值: "<<s1->getNum()<<endl; cout<<"更改后a的num值: "<<a.getNum()<<endl; }
在调用changeA时,注意并非引用调用,而是直接把sample对象传递进来。
输出结果为:
可以看出,changeA的形参并没有显示指定是指针,也不是引用,但是却真实得改变了s1指向对象的值。
由于s1(a)时执行了构造函数构造了新的对象,因此s1与a其实无关,所以a的值不变。
所以在使用句柄类时需要牢记自己操作的是指针,虽然看起来像对象,但是本质是指针。
参考:
https://blog.csdn.net/xgf415/article/details/52962875
https://blog.csdn.net/qingcaichongchong/article/details/7559801