智能指针总结


  智能指针并非c++11的原创,Boost库很早就提供了share_ptr和weak_ptr,c++11在此基础上增加了unique_ptr,从而形成了我们现在所说的智能指针。

  智能指针主要用于管理动态内存,当智能指针过期时,这些内存将自动释放;智能指针也是模板,当我们创建智能指针时,需要提供其所指向的类型;

  可以从 auto_ptr 的构造函数来了解一下智能指针的基本构成:

template<class X> class atuo_ptr{
    public:
    explicit auto_ptr(X* p=0) throw;
}

  从上面可以看出,智能指针中保存了一个指针成员,在构造函数中传入赋值;目前 auto_ptr 已经被弃用,主要原因在于:当 auto_ptr 进行赋值时,将会进行所有权转让,但是失去所有权的指针将不再指向有效数据,如果此时还要使用该指针就会出现问题;

shared_ptr

  shared _ptr 允许多个指针指向同一个对象;其基本原理是:使用引用计数,赋值时,计数将加 1,指针过期时,计数将减 1,当最后一个指针过期时,才调用指针成员的析构函数进行析构;

  指向相同资源的所有 shared _ptr 共享“引用计数管理区域”,并采用原子操作保证该区域中的引用计数被互斥的访问,该“引用计数管理区域”由其基类指针指向它,当我们进行 shared _ptr 的拷贝时,我们可以认为该基类指针使用的是一种浅拷贝,也就是无论拷贝多少次,“引用计数管理区域”都不会被重新创建,而是引用计数增 use _count _增 1,反之 use _count _减 1;(参考链接)

shared_ptr 的操作函数

  shared_ptr的构造函数将使引用计数加1,析构函数使引用计数减1,这个不必再赘述,但我们需要注意拷贝构造函数与赋值运算符对引用计数带来的不同,考虑下面代码将会带来什么效果;

shared_ptr<T> p(new T());
shared_ptr<T> q(new T());
p = q;  //#3
shared_ptr<T> p1(p);  //#4

  拷贝构造函数(#4)将会增加 p 所指向对象的引用计数;赋值运算符(#3)可以视作 q 所指向的对象对 p 所指向的对象进行了覆盖,所以此操作会递减 p 所指向对象的引用计数,而会递增 q 的引用计数;

shared_ptr的线程安全性

  shared_ptr的引用计数本身是安全且无锁的,一个shared_ptr可以被多个线程安全读取,但是如果有写操作则需要加锁,否则可能会有race condition 发生,建议使用原子操作:std::atomic_…std::shared_ptr;

shared_ptr的工厂函数

  shared_ptr构造时需要显式的调用new,但是shared_ptr也提供了一个消除了显式调用new的函数,就是make_shared(),我们可以像下面这样使用:

shared_ptr<string> sp = make_shared<string>("make_shared");

  make_shared()函数最多可以接收10个参数,然后将它们传递给类型T,make_shared()的创建比直接使用构造函数要更快,因为它内部只分配一次内存,消除了shared_ptr构造时的开销;

shared_ptr应用于标准容器

  我们当然可以这样使用shared_ptr和容器:shared_ptr<list >;但是更多的我们是希望智能指针作为容器元素,在这一点上,shared_ptr能够和标准容器完美配合,因为标准容器要求元素可赋值、可复制,如果要排序,还要可比较,而shared_ptr确实符号上述需求。此外还有一个便利就是:我们不需要写额外的大量代码来保证指针最终被正确删除,如果使用普通指针存放在容器中这是需要的,但是shared_ptr省略了这一步,并不需要担心资源泄露。示例如下:

#include <stdio.h>
#include <vector>
#include <iostream>
#include <memory>
#include <vector>

using namespace std;
int main(int argc, char *argv[])
{
    typedef vector<shared_ptr<int> > vs;
    vs v(10);

    int i = 0 ;
    for(vs::iterator pos = v.begin(); pos != v.end() ; ++pos)
    {
        (*pos) = make_shared<int>(++i);
        cout << *(*pos) << ",";
    }
    cout << endl;

    shared_ptr<int> p = v[9];
    *p = 100;
    cout << *v[9] << endl;

    return 0;
}

shared_ptr应用于工厂模式

  智能指针和工厂模式简直就是绝配,不再返回一个原始指针,而是一个被shared_ptr包装的智能指针,可以很好的保护系统资源,更好地控制对接口地调用,这也是《Efective C++》中强力推荐地使用方式之一。

shared_ptr定制删除器

  有时候,我们或许想要定制智能指针的delete行为,比如:一个网络socket,使用完毕,我们不但要delete掉它,还要关闭它,那么该怎么做呢?无疑,我们可以在socket中析构函数中实现,但是问题在于,socket并非我们实现的,而是第三方库所提供的,当然,在管理socket的类中去控制关闭也是可以的,但是我们或许还需要一种更加智能的方式,于是,shared_ptr定制器就可以派上用场了。
  得益于shared_ptr提供了下面这个构造函数:shared_ptr<Y *p,D d>,我们可以将删除器的对象或者函数指针作为第二个参数传入,只要它能够使得d( p )成立即可,对删除的要求是他必须是可拷贝的,同时行为也类似于delete。一旦定义了删除器,那么shared_ptr在析构时将会调用其进行析构而非delete;示例代码如下:

class socket_t(...);
socket_t* open_socket()
{
	...
}

void close_socket(socket_t* s)
{
	...
}

socket_t* s = open_socket();
shared_ptr<socket_t> p(s,close_socket); //传入删除器

unique_ptr

  在同一时刻只能有一个 unique_ptr 指向给定对象,当我们进行赋值时会进行所有权让,所以 unique_ptr 和 auto_ptr 比较类似,但是如果只是如此,那么将无法避免 auto_ptr 的缺陷,所以 unique_ptr 对此进行了修正:当程序试图将一个 unique_ptr 赋给另一个时,如果源 unique_ptr 是个临时右值,编译器允许这样做,但是如果 unique_ptr 将存在一段时间,编译器将禁止这么做;

unique_ptr<string> p3(new string("auto"));
unique_ptr<string> p4;
p4 = p3;  //#6

  编译器会认为#6 非法,避免了 p3 不再指向有效数据的问题;

unique_ptr<string> demo(const char* s)
{
    unique_ptr<string> temp(new string(s));
    return temp;
}

unique_ptr<string> ps;
ps = demo("Uniquely special");

  上述操作允许,因为 ps 接管了所有权,而原有 unique_ptr 对象被销毁了;

weak_ptr

  weak_ptr 是一种弱引用,指向 shared_ptr 所管理的对象;将一个 weak_ptr 绑定到一个 share_ptr 不会改变 shared_ptr 的引用计数,一旦最后一个指向对象的 share_ptr 被销毁,对象就会被释放,即使有 weak_ptr 指向对象,对象还是会被释放,因此它是一种弱共享;

  我们可以像下面这样使用 weak_ptr:

#include <memory>
using namespace std;

int main()
{
    int *pNum = new int;
    shared_ptr<int> p1(pNum);

    weak_ptr<int> p2(p1); //等价于p2 = p1;
    
    
    if(p2.expired()) //weak_ptr指向的对象是否已被销毁
    {
        //...
    }

    if(shared_ptr<int> p3 = p2.lock())
    {
        //...
    }
    
    return 0;
}

  需要注意的是:我们不能使用 weak_ptr 直接访问对象,因为 weak_ptr 并没有重载 operator->和 operator *操作符,因此不可直接通过 weak_ptr 使用对象,典型的用法是调用其 lock 函数来获得 shared_ptr 示例,进而访问原始对象;

  同时由于 weak_ptr 所指向的对象有可能不存在,因此上述代码使用了 lock,该函数的作用是:如果weak_ptr所指向的shared_ptr对象已经析构,那么lock返回空,否则返回一个shared_ptr对象,该对象会导致weak_ptr所指向的shared_ptr引用计数+1;

  那么 weak_ptr 到底有什么用呢?从上面我们可以看出,使用 weak_ptr 是通过间接调用 share_ptr,那么为什么不直接使用 share_ptr 就好了呢?

  weak_ptr 是为了解决 shared_ptr 存在的环形引用问题;

class Parent
{
public:
    shared_ptr<Child> child;
}

class Child
{
public:
    shared_ptr<Parent> parent;
}

shared_ptr<Parent> pA(new Parent);
shared_ptr<Child> pB(new Child);
pA->child = pB;
pB->parent = pA;

  上述代码将会存在这样一个困境:两个对象各自包含指向彼此的 shared_ptr 成员,形成环状引用,引用计数永远为 1,不能销毁,造成内存泄漏;

  如果我们将上述代码中的类成员的 shared_ptr 使用 weak_ptr 代替,即如下所示:

class Parent
{
public:
    weak_ptr<Child> child;
}

class Child
{
public:
    weak_ptr<Parent> parent;
}

shared_ptr<Parent> pA(new Parent);
shared_ptr<Child> pB(new Child);
pA->child = pB;
pB->parent = pA;

  那么根据 weak_ptr 的特性,其不会增加 shared_ptr 的引用计数,从而打破了 shared_ptr 的环状引用问题;

智能指针与常规指针的区别

  智能指针和常规指针确实很类似,但是还是存在一定区别,例如:

shared_ptr<string> p = new string();

  上述赋值并不能成立,因为 p 是智能指针,而非指针,智能指针中包含指针成员,只能通过其构造函数来初始化,当没有初始化时,该指针指向空;

智能指针的选择

  • 如果程序要使用多个指向同一个对象的指针,那么应选择 shared_ptr;这样的情况包括:有一个指针数组,并使用一些辅助指针来标识特定的元素,如最大的元素和最小的元素;两个对象对包含指向第三个对象的指针:STL 容器包含的指针;
  • 如果程序不需要多个指向同一个对象的指针,可以使用 unique_ptr,如果函数使用 new 分配内存,并返回指向该内存的指针,将其返回类型声明为 unique_ptr 是一个不错的选择;
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值