【C++11智能指针】unique_ptr
概述
一个 unique_ptr “拥有”它所指向的对象。
与 shared_ptr 不同,某个时刻只能有一个 unique_ptr 指向一个给定对象。
当 unique_ptr 被销毁时,它所指向的对象也被销毁。
初始化
直接初始化
unique_ptr<int> p1; // 可指向int对象的一个空智能指针p1
unique_ptr<int> p2(new int(100)); // 指向int对象的智能指针p2,int对象的值为100
auto p3(new int(100)); // 不能简写为auto,这里auto推断出p3是普通指针
make_unique函数
unique_ptr<int> p1 = make_unique<int>(100); // 指向int对象的智能指针p2,int对象的值为100
auto p2 = make_unique<int>(200); // 指向int对象的智能指针p2,int对象的值为100
std::make_shared是C++11的一部分,可惜的是,std::make_unique不是,它在C++14才纳入标准库。
make_unique 不支持指定删除器。如果不用删除器,建议优先使用 make_unique,拥有更高的性能。
拷贝构造与拷贝赋值
由于一个 unique_ptr 拥有它指向的对象,因此 unique_ptr 不支持普通的拷贝构造和拷贝赋值操作:
#include <iostream>
#include <memory>
using namespace std;
int main()
{
unique_ptr<string> p1(new string("hello"));
unique_ptr<string> p2(p1); // 错误:unique_ptr不支持拷贝构造
unique_ptr<string> p3 = p1; // 错误:unique_ptr不支持拷贝赋值
return 0;
}
移动构造与移动赋值
#include <iostream>
#include <memory>
using namespace std;
int main()
{
unique_ptr<string> p1(new string("hello"));
unique_ptr<string> p2(std::move(p1)); // 移动构造一个新的智能指针p2,
// p1变成空,p2指向该对象
unique_ptr<string> p3 = std::move(p1); // 移动赋值,
// p1变成空,p3指向该对象
return 0;
}
常用操作
release()
p.release() 返回裸指针,同时将该智能指针 p 置空。也就是说,调用 release() 会切断 unique_ptr 和它原来管理的对象间的联系。
release() 返回的指针通常被用来初始化另一个智能指针或给另一个智能指针赋值。
#include <iostream>
#include <memory>
using namespace std;
int main()
{
unique_ptr<string> p1(new string("hello"));
unique_ptr<string> p2(p1.release()); // release()返回p1当前指向对象的裸指针,并将p1置为空。
// p2通过返回的裸指针初始化,唯一拥有该对象,即将对象所有权从p1转移给p2。
return 0;
}
在上面代码中,管理内存的责任简单地从一个智能指针p1转移给另一个智能指针p2。但是,如果我们不用另一个智能指针来保存release() 返回的指针,仅仅切断了p1与对象的内存管理责任,我们的程序就要负责内存的释放:
#include <iostream>
#include <memory>
using namespace std;
int main()
{
unique_ptr<string> p1(new string("hello"));
// p1.release(); // 错误!!程序没有进行内存的释放,导致内存泄漏
string* p = p1.release(); // 可以简写为auto p = p1.release();
delete p; // 手动delete释放
return 0;
}
虽然我们不能拷贝或赋值 unique_ptr,但可以通过调用 release() 或 reset() 将指针的所有权从一个(非const)unique_ptr 转移给另一个 unique_ptr。
get()
p.get():返回智能指针p中保存的裸指针。因为有些第三方库的函数参数需要的是内置裸指针,所以引入该函数。
注意:release()也返回指针p中保存的裸指针,并且切断了p与原指向对象的联系,p置为空,但原对象依然存在;get()只返回p中保存的裸指针,并未切断p与指向对象的联系,仍“拥有”原对象。
#include <iostream>
#include <memory>
using namespace std;
int main()
{
unique_ptr<string> up(new string("hello"));
string* p = up.get();
*p = "world";
//delete p; // 这里不要delete,up仍拥有原对象
// 否则会产生不可预料的后果
return 0;
}
reset()
无参数:reset()
若 reset 不带参数:释放智能指针所指向的对象,并将智能指针置空。
#include <iostream>
#include <memory>
using namespace std;
int main()
{
unique_ptr<string> p1(new string("hello"));
p1.reset(); // 释放指向的对象,指针p1置为空
return 0;
}
有参数reset(ptr)
若 reset 带参数:释放智能指针所指向的对象,并让该智能指针指向新对象。
#include <iostream>
#include <memory>
using namespace std;
int main()
{
unique_ptr<string> p1(new string("hello"));
p1.reset(new string("world")); // p1释放原指向对象的内存,指向新对象
unique_ptr<string> p2(new string("world"));
p1.reset(p2.release()); // release()返回p2当前指向对象的裸指针,并将p2置为空
// reset()释放p1原指向对象的内存,指向裸指针指向的对象
return 0;
}
= nullptr
p = nullptr:释放智能指针所指向的对象,并将智能指针置空。
**注意:**与无参数reset()作用相同。
#include <iostream>
#include <memory>
using namespace std;
int main()
{
unique_ptr<string> p(new string("hello"));
p = nullptr; // 释放p所指向的对象,并将p置空
return 0;
}
swap()
交换两个智能指针所指向的对象。
#include <iostream>
#include <memory>
using namespace std;
int main()
{
unique_ptr<string> p1(new string("hello"));
unique_ptr<string> p2(new string("world"));
p1.swap(p2); // 等价于std::swap(p1, p2);
return 0;
}
指向一个数组
#include <iostream>
#include <memory>
using namespace std;
class A
{
public:
A() { cout << "A()" << endl; }
~A() { cout << "~A()" << endl; }
};
int main()
{
unique_ptr<A[]> p(new A[10]); // 如果有自己的析构函数,那么定义时<>里面必须加上[]
// 否则会报异常
unique_ptr<int[]> p(new int[10]);
p[0] = 110;
p[1] = 120;
return 0;
}
解引用
*p 解引用:获取该智能指针指向的对象,可以直接操作。
如果定义的内容是数组,没有解引用运算符。
#include <iostream>
#include <memory>
using namespace std;
int main()
{
unique_ptr<string> p1(new string("hello"));
*p1 = "world";
unique_ptr<int> p2(new int(100));
*p2 = 200;
unique_ptr<int[]> p(new int[10]); // 如果定义的内容是数组,没有解引用运算符
return 0;
}
转换为shared_ptr
如果 unique_ptr 为右值,就可以将它赋值给 shared_ptr。
因为 shared_ptr 包含一个显式构造函数,可用于将右值 unqiue_ptr 转换为 shared_ptr,shared_ptr 将接管原来归 unique_ptr 所拥有的对象。
#include <iostream>
#include <memory>
using namespace std;
auto myfunc()
{
return unique_ptr<string>(new string("hello")); // 临时对象都是右值
}
int main()
{
shared_ptr<string> sp = myfunc(); // 这里系统会为shared_ptr创建控制块
unique_ptr<string> up(new string("world"));
shared_ptr<string> sp = std::move(up);
return 0;
}
返回unique_ptr
不能拷贝 unique_ptr 的规则有一个例外:我们可以拷贝或赋值一个将要被销毁的 unique_ptr。最常见的例子是从函数返回一个 unique_ptr。
如果没有智能指针去接返回的unique_ptr,临时对象会被释放,同时会释放掉所指向的对象的内存.
#include <iostream>
#include <memory>
using namespace std;
unique_ptr<int> fun(int value)
{
unique_ptr<int> p(new int(value));
return p; // 返回局部对象,系统会生成一个临时unique_ptr对象
// 然后调用unique_ptr的移动构造函数
}
int main()
{
unique_ptr<int> up = fun(120); // 如果用up来接,则临时对象直接构造在up里。
// 如果不接,则临时对象会被释放,同时会释放掉所指向的对象的内存。
return 0;
}
删除器
unique_ptr 指定删除器,先要在类型模板参数中传递进去类型名,然后在构造参数中再给具体的删除器函数名。
#include <iostream>
#include <memory>
using namespace std;
void mydeleter(string* pdel)
{
delete pdel;
pdel = nullptr;
}
int main()
{
typedef void(*fp)(string*); // 定义一个函数指针类型,类型名为fp
unique_ptr<string, fp> up(new string("hello"), mydeleter);
return 0;
}
即使两个 shared_ptr 指定的删除器不相同,但只要他们所指向的对象相同,那么这两个 shared_ptr 的类型相同,即可以放到同一个容器里。
但是 unique_ptr 不一样,如果两个 unique_ptr 指定的删除器不同,则相当于 这两个unique_ptr 的类型不同,即不能放到同一个容器里。
unique_ptr的大小
通常情况下,unique_ptr 的大小跟裸指针一样。
如果增加了自己的删除器,则 unique_ptr 的尺寸可能增加,也可能不增加。如果 lambda 表达式作为删除器,尺寸就没变化。如果定义一个函数作为删除器,尺寸发生变化。
对比shared_ptr:shared_ptr不管指定什么删除器,shared_ptr 的尺寸都是裸指针的 2 倍。
参考资料
【C++11智能指针】unique_ptr概述、初始化、常用操作、返回unique_ptr、指定删除器、尺寸
C++智能指针shared_ptr、unique_ptr以及weak_ptr