现在的位置: 首页 > 自动控制 > 工业·编程 > 正文

(一)智能指针—-auto_ptr/scope_ptr/unique_ptr

2017-06-21 11:27 工业·编程 ⁄ 共 3365字 ⁄ 字号 暂无评论

在C++中我们为了避免内存泄漏而引入的智能指针

因为在C++中我们频繁使用堆内存,但是堆内存的申请和释放都由程序员自己管理。程序员自己管理堆内存可以提高了程序的效率,但是整体来说堆内存的管理是麻烦的,C++11中引入了智能指针的概念,方便管理堆内存。我们使用普通指针,容易造成堆内存泄露(忘记释放),二次释放,程序发生异常时内存泄露等问题,使用智能指针能更好的管理堆内存。实际上我们这个智能指针即对象因为对象在程序结束时会自动调用其析构,就不会发生内存泄漏了,这也是我们实现智能指针一个重要的基础思想

对智能指针的描述

智能指针是对普通指针用面向对象的类进行的一个封装,这使得智能指针实质是一个对象,行为表现的却像一个指针。

智能指针的智能就在于它能够在适当的时机安全的帮助你释放内存

智能指针主要是运用了RAII技术(也称为“资源获取就是初始化”,是c++等编程语言常用的管理资源、避免内存泄露的方法。它保证在任何情况下,使用对象时先构造对象,最后析构对象)

1:auto_ptr

auto_ptr是C++98标准化引入的,auto_ptr十分简单,但使用起来却到处是坑,所以不提倡大家使用,C++11已放弃该关键字。低版本的VS并没有将其加入标准库(boost)。

在auto_ptr中构造函数是浅拷贝,将原有资源转给最新的对象,将自己置为空。auto_ptr的赋值也是一样,会将自身置空,将最新的指针指向原有空间,所以auto_ptr是不允许赋值的,我们程序员要注意这点。

在容器中我们要注意不要使用auto_ptr,因为容器操作涉及很多拷贝构造,不允许浅拷贝

模拟代码如下:

typedef struct TestAutoPtr    //一个作为测试的结构体而已。。。

{

    TestAutoPtr(int val=int()):mval(val){}

    int mval;

}tap;

  

template<typename T>

class AutoPtr

{

public:

    //构造函数

    AutoPtr(T *a=NULL):ptr(a){}

    //拷贝构造,转交权限,所以参数不加const

    AutoPtr(AutoPtr &rt):ptr(rt.ptr)

    {

        rt.ptr=NULL;

    }

    //赋值运算符重载,因为移交了管理权,所以auto_ptr是不允许赋值的

    AutoPtr& operator=(AutoPtr &rt)

    {

        if(&rt!=this)

        {

            if(ptr!=NULL)   //这个判断不要也行,delete NULL不会出错

            {

                delete ptr;

                ptr=NULL;

            }

            ptr=rt.ptr;

            rt.ptr=NULL;   //权限转移,自身赋为空

        }

        return *this;

    }

    //重载*运算符

    T &operator *()

    {

        return *ptr;

    }

    //重载->运算符

    T *operator ->()

    {

        return ptr;

    }   

        //析构函数   

    ~AutoPtr()

    {

        if(ptr!=NULL)

        {

            delete ptr;

            ptr=NULL;

        }

    }

private:

    T *ptr;

};

  

测试用例:

int main()

{

    AutoPtr<int> a1(new int(2));

    AutoPtr<int> a2=a1;   //此时a1已指向空

        //cout<<*a1<<endl;   //a1指向空,解引用就崩了

  

    int *p=new int(5);

    AutoPtr<int> a3(p);

    a2=a3;   

    cout<<*a2<<endl;    //5

    //cout<<*a3<<endl;  //a3指向空,解引用就崩了

  

    AutoPtr<tap> a4(new tap(8));

    cout<<a4->mval<<endl;   //相当于a4.operator->()->mval

  

    /*

        int *q=new int(6);

        AutoPtr<int> a5(q);  //因为a5与a6指向同一块空间,析构的时候就会将那块空间释放两次,会发生错误

        AutoPtr<int> a6(q);

        cout<<*a5<<endl;   //6

        cout<<*a6<<endl;   //6

    */

}

2:scope_ptr

它是boost库中的,scope_ptr的用法与auto_ptr几乎一样,大多数情况下它可以与auto_ptr相互替换,也可以从一个auto_ptr获得指针的管理权(同时auto_ptr失去管理权)。scope_ptr与auto_ptr的根本区别在于指针的所有权。auto_ptr特意被设计为指针的所有权是可转移的,可以在函数之间传递,但是同一个时刻只能有一个auto_ptr管理指针,scope_ptr把拷贝构造函数和赋值函数都声明为私有的,拒绝了指针的所有权转让,任何人都无权访问被管理的指针,从而保证了指针的绝对安全。

概括来说就是:scope_ptr不允许拷贝,赋值,只能在scope_ptr被声明的作用域内使用,除了*和->外scope_ptr也没有定义其他的操作符(不能对scope_ptr进行++或者--等指针算术操作),这样就不用考虑浅拷贝的问题了。

同样,因为其不能进行拷贝和赋值,所以不能用作容器的元素

3:unique_ptr

来源于boost库,实际上unique_ptr与auto_ptr很像,都是让最后一个指针指向资源,将之前的指针置为空。但是unique_ptr与auto_ptr还是有些不同,且看下文。

无法直接拷贝构造和赋值

unique_ptr<int> s1(new int(5));

unique_ptr<int> s2(s1);  //不允许拷贝构造,编译器报错

unique_ptr<int> s3(new int(6));

s1= s3;  //不允许赋值,编译器报错

可以进行移动赋值和移动构造(间接)

因为它没有办法直接进行拷贝构造和赋值,那么我们如何转移管理权呢?

unique_ptr<int> GetVal()   //作为返回值是可以进行拷贝的,注意不能返回&,要生成临时对象,否则出错

{

    unique_ptr<int> p(new int(8));

    return p;

}

  

int main()

{

    unique_ptr<int> s4 = GetVal();  //成功进行了拷贝

}

实际上,上面的转移管理的代码我们还可以这样写:

int main()

{

    unique_ptr<int> s4(new int(5));

    unique_ptr<int> s5 = std::move(s4);  //std::move将s4的内存转移给了s5,s4就被指向了空,编译器不会报错,std::move时C++11出现的

    cout<<*s5<<endl;   //5

    //cout<<*S4<<endl;   //程序崩溃

}

可以作为容器元素

我们知道auto_ptr和scope_ptr都不可做为容器元素,而unique_ptr也同样不能直接做为容器元素,但可以通过间接作为容器元素

int main()

{

    unique_ptr<int> s6(new int(10));

    vector<unique_ptr<int>> vec;

    /*

    编译不通过,不能直接插入s6,

    push_back(s6);

    */

    vec.push_back(std::move(s6));  //成功插入

    cout << *s6 << endl;//这个出错了,说明添加到vec容器中,s6本身就作废了

}

给我留言

留言无头像?