C++智能指针

Effective C++ Item 13,以对象管理资源,提到了两个关键点:

  • 获得资源后立即放进管理对象
  • 管理对象运用析构函数确保资源被释放

智能指针就是为了迎合以上两点,作用就是 便于内存管理,确保程序不存在内存和资源泄露,并且是异常安全的。


C++存有四种智能指针

  • auto_ptr

auto_ptr , C++11已经弃用。auto_ptr管理的资源 绝对没有一个以上的 auto_ptr 指向这份资源,也就是说 复制一个 auto_ptr 时,被复制的 auto_ptr 会指向为空。

auto_ptr<int> p1(new int(10));

auto_ptr<int> p2 = p1;

cout << p1.get() << endl; // output: 0, 即 p1 = nullptr
  • unique_ptr

unique_ptr , auto_ptr 的替代品,与 auto_ptr 一样拥有唯一拥有权的特性,不过,与 auto_ptr 不一样的是,unique_ptr 没有复制构造函数,这样可以防止使用者不小心转移了拥有权。如果想转移拥有权,需要显示调用 move 函数。所以,函数传参值传递的时候也需要显示调用 move 函数。但是,函数的返回值已经进行了 move ,所以不需要显示调用 move 函数。

unique_ptr<int> fun(unique_ptr<int> p)
{
    return p;
}

int main()
{
    shared_ptr<A> arrayObj(new A[5], [](A *p) {
        delete[] p;
    });

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

    // unique_ptr<int> p1 = p;  //! error: use of deleted function...

    unique_ptr<int> p2 = move(p);

    unique_ptr<int> p3 = fun(move(p2));

    return 0;
}

unique_ptrrelease() 方法虽然不会销毁 unique_ptr 指向的资源,但是调用 release() 方法后,unique_ptr 就会从管理资源的责任重解脱出来,也就是说,此时必须要手动销毁资源。

  • share_ptr

share_ptr , 采用引用计数的智能指针,多个 share_ptr 可以共享资源,如果一个 share_ptr 放弃资源的”所有权”,其他 share_ptr 对资源的引用并不会发生变化。只有在引用计数为0的时候,share_ptr 才会释放资源。

void fun(shared_ptr<int> p)
{
    cout << "ref count:" << p.use_count() << endl;  // 函数传参,值传递,所以引用数+1
}

int main ()
{
    shared_ptr<int> p1(new int(10));
    cout << "ref count:" << p1.use_count() << endl;

    shared_ptr<int> p2 = p1;
    cout << "ref count:" << p1.use_count() << endl;

    fun(p1);
    cout << "ref count:" << p1.use_count() << endl;  // 函数退出,参数的 share_ptr 自动销毁,引用数-1

    p2.reset();
    cout << "ref count:" << p1.use_count() << endl;
    p3.reset();
    cout << "ref count:" << p1.use_count() << endl;  // 此时只有p1指向资源,只有一份引用

    return 0;
}

output:
ref count:1
ref count:2
ref count:3  // 函数传参,值传递,所以引用数+1
ref count:2  // 函数退出,参数的share_ptr自动销毁,引用数-1
ref count:2
ref count:1  // 此时只有p1指向资源,只有一份引用

使用 share_ptr 的时候要注意避免使用 get() 方法获取和使用裸指针,因为使用裸指针的时候,可能会不经意间手动 delete 了资源对象,所以,当 share_ptr 去试图销毁管理的资源时,导致ACCESS VIOLATION

shared_ptr<int> a(new int(10));
int * pa = a.get();
delete pa;
//! ACCESS VIOLATION

share_ptr 将调用 delete 释放内存,所以当 share_ptr 指向数组对象的时候,传递给 share_ptr 一个自定义的 delete 方法。可通过 lambda 表达式完成。

class A {
public:
    A()
    {
        cout<<"constructor"<<endl;
    }
    ~A()
    {
        cout << "destructor"<<endl;
    }
};

int main()
{
    shared_ptr<A> arrayObj(new A[5], [](A *p) {
        delete[] p;
    });

    return 0;
}

share_ptr 已经足够好足够用了,但是还是可能出现问题,比如下面的代码,就会出现 环形引用问题,如下代码,A和B的对象各有一份,且引用数为2,程序结束时,即使会销毁智能指针,但是由于引用数不为0,对象并不能被回收,造成内存泄漏。

class B;

class A {
public:
    shared_ptr<B> p_b;
};

class B {
public:
    shared_ptr<A> p_a;
};

int main()
{
    shared_ptr<A> a(new A);
    shared_ptr<B> b(new B);

    a->p_b = b;
    b->p_a = a;

    return 0;
}  
  • weak_ptr

要解决环形引用问题并没有好的解决办法,除了在编码过程中注意,也可以在可能出现环形引用的地方使用 weak_ptrweak_ptr 比较特殊,它可以指向 share_ptr 指向的资源,但是却不拥有该资源。可以通过 weak_ptr 对象的成员函数 lock() 返回指向该资源的一个 share_ptr 对象。如果 weak_ptr 指向的资源已经无效时,会返回一个空值 nullptr 。所以使用 lock() 时,要注意返回的 share_ptr 是否有效。由于 weak_ptr 是指向 share_ptr 指向的资源,所以 weak_ptr 不能独立存在。

void fun(weak_ptr<int> & wp)
{
    shared_ptr<int> p = wp.lock();
    if (p != nullptr) {
        cout << *p << endl;
    } else {
        cout << "Pointer is invalid." << endl;
    }
}

int main()
{
    shared_ptr<int> p1(new int(10));
    shared_ptr<int> p2 = p1;
    weak_ptr<int> wp = p1;
    cout << "ref count: " << p1.use_count() << endl;

    fun(wp);

    p1.reset();
    fun(wp);

    p2.reset();
    fun(wp);

    return 0;
}

/**
output:
ref count: 2
10
10
Pointer is invalid.
**/

上面提到的 环形引用 问题可以采取如下方式解决。此时程序退出后,对象会被正确回收。

class B;

class A {
public:
    weak_ptr<B> p_b;
};

class B {
public:
    weak_ptr<A> p_a;
};

int main()
{
    shared_ptr<A> a(new A);
    shared_ptr<B> b(new B);
    a->p_b = b;
    b->p_a = a;

    return 0;
}

对话与讨论