C++智能指针unique_ptr

unique_ptr

unique_ptr,独享指针,一种零开销的智能指针,既可以帮你自动管理内存,又没有额外的性能损耗,非常推荐在任何适合的情况下使用。
unique_ptr,独享指针,顾名思义,unique_ptr独占某个资源的管理权,当unique_ptr销毁或者reset的时候,其绑定的资源也会自动释放。这样可以极大减轻手动释放内存的烦恼。
既然是独享,那么就不能有两个unique_ptr同时指向一份资源,unique_ptr也不能复制。

unique_ptr管理对象的生命周期

定义一个类Ball


class Ball
{
public:
    Ball() { cout << "A ball appears." << endl; }
    ~Ball() { cout << "A ball disappears." << endl; }
    void Bounce() { cout << "A ball jumps." << endl; }
};

然后使用unique_ptr动态分配内存


#include <memory>
#include <iostream>

using namespace std;

void func()
{
    cout << "Now in func." << endl;
    //初始化方式和共享指针类似
    unique_ptr<Ball> up {make_unique<Ball>()};
    up->Bounce();
    cout << "Before quiting func." << endl;
}

int main()
{
    cout << "Now in main." << endl;
    func();
    cout << "End Executing func." << endl;
}

执行结果


Now in main.
Now in func.
A ball appears.
A ball jumps.
Before quiting func.
A ball disappears.
End Executing func.
  1. 进入main函数,首先会打印Now in main.
  2. 执行func(),打印Now in func.
  3. 用make_unique创建了一个Ball并绑定了一个unique_ptr up,因此会调用Ball的构造函数,打印a ball appears.
  4. 用up显式调用Ball的Bounce方法,打印a ball jumps.
  5. 在func结束执行之前,我们打印Before quiting func. 此时up未被销毁,底下的资源也未被释放。
  6. func结束执行后,局部变量up被销毁,其绑定的Ball也被释放,因此打印a ball disappears.
  7. 执行执行main函数的语句,打印End Executing func.

上面这个例子清晰展示了unique_ptr和其绑定资源的声明周期,可以看出,当unique_ptr销毁的时候,其绑定的资源就会自动释放,不用我们再手动delete,非常方便。

你不需要学习很多新语法就能获得巨大的好处:

在相应的场景下,你不需要再手动释放内存
避免一些因为异常引起的bug

避免异常引起的bug

这段代码虽然手动delete了p,但是仍然可能造成内存泄漏。因为如果p→foo()这一步如果抛出了异常,delete这句话可能不会执行。


void some_func()
{
    Object* p = { new Object{} };
    p->foo();
    delete p;
}

如果我们用unique_ptr就没有这个问题,即便抛出异常,资源也会释放

void some_func()
{
    unique_ptr<Object> up { make_unique<Object>() };
    up->foo();
}

使用unique_ptr

用法1:同裸指针的用法

使用unique_ptr的方法跟裸指针是类似的,*、→等运算符都适用。比如

auto ball = make_unique<Ball>();
ball->Bounce();
(*ball).Bounce();

用法2:获得独享指针的裸指针

如果你想获得资源的裸指针,你可以用get获得

Ball* p = up->get();

这个在某些情况下有用,比如某个函数的参数只接受裸指针。

用法3:释放独享指针

你可以用reset来释放unique_ptr下的资源,或者释放的同时指向另一份资源。

up.reset();
up.reset(new Ball{});

第一行会释放up下的资源并把up设置为nullptr,第二行会释放up下的资源,然后让up指向一个新的Ball实例。

用法4:解绑并返回给一个裸指针

Ball* ball = up.release();
delete ball;
ball = nullptr;

你可以用release()把unique_ptr跟资源解绑,release()返回资源的裸指针,同时把unique_ptr设置成nullptr,此时你需要手动管理内存。
如果你把一个unique_ptr赋值为nullptr,也会释放底下的资源

疑惑:这里ball从一个裸指针释放赋给了一个裸指针,那第3行应该说是把裸指针ball设置成nullptr吧

unique_ptr控制权

由于unique_ptr独占一块内存的控制权,所以它不支持普通拷贝

unique_ptr<int> up1 = make_unique<int>(100);
unique_ptr<int> up2(up1); // error
up2 = up1; // error

转移控制权方式1:release释放

第2、3行编译的时候都会报错,因为不能复制控制权。但是呢,我们可以转移控制权,如下
up1.release()释放了up1的控制权,返回了资源的裸指针,并用来给up2初始化,up2获得了资源的控制权。


unique_ptr<int> up1 = make_unique<int>(100);
unique_ptr<int> up2(up1.release());

转移控制权方式2:move

你还可以使用std::move来达成同样的效果


unique_ptr<int> up1 = make_unique<int>(100);
unique_ptr<int> up2 = move(up1);

自定义释放函数(没看懂)

默认情况下,unique_ptr使用new和delete来分配和释放内存。你可以自定义分配函数和释放函数来替换这两个操作


...
int* my_alloc(int v)
{
    cout << "Allocating " << v << endl;
    return new int{v};
}

void my_dealloc(int *p)
{
    cout << "Deallocating " << *p << endl;
    delete p;
}

int main()
{
    unique_ptr<int, decltype(&my_dealloc)> cup {my_alloc(100), my_dealloc};
}

执行结果


Allocating 100
Deallocating 100

这里定义unique_ptr的时候语法相比之前更复杂,需要在模板参数里面声明自定义释放函数的类型,这里用decltype(&my_dealloc)完成这个操作,它是个函数指针,指向自定义的释放函数。
分配内存的时候使用my_alloc(),因此先打印Allocating 100再调用new申请内存。释放的时候先打印Deallocating 100,再delete。
可以看出,unique_ptr使用自定义删除函数的时候,比shared_ptr更复杂。
原因在于,unique_ptr绑定释放函数是在编译期,避免了运行时绑定的时间损耗,删除函数的类型本身变成unique_ptr实例的一部分。这是unique_ptr的零额外开销特性决定的。
shared_ptr的自定义函数在运行时绑定,用户用起来更简单,由于shared_ptr的引用计数已经有运行时消耗了,再增加一点也就无所谓了。
出于这种考虑,unique_ptr和shared_ptr对于自定义释放函数做出了不同的设计。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值