C++中的句柄类(智能指针)

最近工作中用到了句柄类,特意了解了一下,后来发现所谓的句柄其实和智能指针就是一回事。为了能够更好的理解句柄类,建议大家先了解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

猜你喜欢

转载自www.cnblogs.com/corineru/p/10941142.html
今日推荐