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.
- 进入main函数,首先会打印Now in main.
- 执行func(),打印Now in func.
- 用make_unique创建了一个Ball并绑定了一个unique_ptr up,因此会调用Ball的构造函数,打印a ball appears.
- 用up显式调用Ball的Bounce方法,打印a ball jumps.
- 在func结束执行之前,我们打印Before quiting func. 此时up未被销毁,底下的资源也未被释放。
- func结束执行后,局部变量up被销毁,其绑定的Ball也被释放,因此打印a ball disappears.
- 执行执行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对于自定义释放函数做出了不同的设计。