C++之智能指针

    • C++之智能指针
      • auto_ptr
      • unique_ptr
      • shared_ptr
      • week_ptr

C++之智能指针

智能指针的核心思想:资源分配即初始化:定义一个类来封装资源的分配和释放,在构造函数完成资源的分配和初始化,在析构函数完成资源的清理,可以保证资源的正确初始化和释放

C++标准库里主要有四种指针:auto_ptr、unique_ptr(scope_ptr)、shared_ptr、weak_ptr
其中auto_ptr是C++98中引入的,其余三种是在C++11标准中被引入。

auto_ptr 是所有智能指针中实现最简单的方式,因为设计问题,它存在诸多缺陷,不提倡使用
unique_ptr(scope_ptr)在auto_ptr的基础上做了改进,弥补了auto_ptr的缺点,特点是unique_ptr“独占”所指向的对象
shared_ptr允许多个指针指向同一对象
weak_ptr是一个伴随类,它是一种弱引用,指向shared_ptr所管理的对象,弥补了shared_ptr的缺陷

auto_ptr

这种智能指针的特点是一旦对其对象进行拷贝构造或者赋值时,就会释放旧指针,这一特点也成为了它的致命缺点:用户在使用它时,必须时刻注意对哪些智能指针进行了指针拷贝构造或赋值操作,否则会导致非法访问内存。

auto_ptr的改进方式是在类中增加一个成员,让该成员作为auto_ptr类对象能否释放资源的标记,这样在拷贝构造或者赋值时,旧的对象仍然指向原来的资源,但是释放资源的权限总是转移到了最近的对象身上。这样做仍然存在一个问题:当大作用域中旧的对象为小作用域中的新对象赋值或者拷贝构造时完毕后,出了小作用域,新对象被析构,资源被释放,而在大作用域中的旧对象中的指针就会成为野指针。

AutoPtr<int> ap1(AutoPtr<int>(new int(20)));
    if (true)
    {
        AutoPtr<int> ap2(ap1);
    }//作用域结束之后ap5已被析构,空间已被释放
    *ap1 = 10;//这时ap2与ap1共享一块空间,既然ap2已被释放,那么ap1对象维护的指针已成为野指针

unique_ptr

因为auto_ptr就是因为转移资源,以及转交权限从而引发一系列的问题的,追根到底其实就是拷贝构造和赋值运算符重载函数导致的。所以这个智能指针就是防拷贝和赋值的。某个时刻只能有一个unique_ptr指向一个给定对象。当unique_ptr被销毁时,它所指向的对象也被销毁。

实现unique_ptr有两种方式:
第一种是将拷贝构造函数和赋值运算符重载声明为私有的,同时只给声明不给定义。

另一种方法是将拷贝构造函数和拷贝赋值运算符定义为删除的函数来阻止拷贝。删除的函数是这样的一种函数:我们虽然声明了它们,但不以任何方式使用它们。在函数的参数列表后面加上=delete来指出我们希望将它定义为删除的:

struct NoCopy{
    NoCopy(const NoCopy &) = delete; //阻止拷贝
    NoCopy &operator = (const NoCopy &) = delete; //阻止赋值 
};

=delete通知编译器,我们不希望定义这些成员函数。=delete必须出现在函数第一次声明的时候。

shared_ptr

与unique_ptr不同,shared_ptr允许多个指针指向同一个对象。在实现上,shared_ptr也是在类中增加了一个成员,但增加的不是权限标记,而是像软连接那样的引用计数器。无论何时我们拷贝一个shared_ptr, 计数器都会递增。当我们给shared_ptr赋予一个新值或者是shared_ptr被销毁时,计数器就会递减。一旦一个shared_ptr的计数器变为0,它就会自动释放自己所管理的对象。
这样一来,就解决了拷贝构造和拷贝赋值后析构时出现的问题。

但是,在某些特定的情况下shared_ptr仍然存在问题:

template<typename T>
struct ListNode{
    struct ListNode *pre;
    struct ListNode *next;
    T data;
}ListNode;

void test()
{
    shared_test>p1(ListNode<int>(10));
    shared_test>p2(ListNode<int>(20));
    cout << p1.use_count << endl; //输出结果为1
    cout << p2.use_count << endl; //输出结果为1
    p1.next = p2;
    p2.pre = p1;
    cout << p1.use_count << endl; //输出结果为2
    cout << p2.use_count << endl; //输出结果为2
}

当test函数运行结束后,p1和p2的生命周期结束,但是此时p1和p2的引用计数被减1后仍然为1,因此p1和p2的所管理的对象没有被释放,导致内存泄露。

什么原因呢?原因在于这两句代码p1.next = p2;p2.pre = p1;
当p1.next指向p2时,p2所维护的计数器就会加1;当p2.pre指向p1时,p1所维护的计数器也会加1。这就是循环引用

为了解决循环引用问题,C++11中出现了shared_ptr的辅助类:week_ptr

week_ptr

week_ptr是一种不控制所指向对象生存周期的智能指针,它指向由一个shared_ptr管理的对象。将一个week_ptr绑定到一个shared_ptr不会改变shared_ptr自身的引用计数。一旦最后一个指向对象的shared_ptr被销毁,对象就会被释放。即使有week_ptr指向的对象,对象还是会被释放。

你可能感兴趣的:(C/C++)