zoukankan      html  css  js  c++  java
  • 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

  • 相关阅读:
    C++使用静态类成员时出现的一个问题
    C++中的const_cast
    【位运算与嵌入式编程】
    电压取反电路
    bzoj4769
    初赛
    noip2011day2
    uva1252
    codeforces 703d
    poj[1734]
  • 原文地址:https://www.cnblogs.com/corineru/p/10941142.html
Copyright © 2011-2022 走看看