C++智能指针unique_ptr

1.unique_ptr概述

一个 unique_ptr “拥有”它所指向的对象。与 shared_ptr 不同,某个时刻只能有一个 unique_ptr 指向一个给定对象。当 unique_ptr 被销毁时,它所指向的对象也被销毁。

在这里插入图片描述

在这里插入图片描述

由于一个 unique_ptr 拥有它指向的对象,因此 unique_ptr 不支持普通的拷贝或赋值操作:

	unique_ptr<string> p1(new string("Stegosaurus"));
	unique_ptr<string> p2(p1); // 错误:unique_ptr不支持拷贝
	
	unique_ptr<string> p3;
	p3 = p2; // 错误:unique_ptr不支持赋值

虽然我们不能拷贝或赋值 unique_ptr,但可以通过调用 release 或 reset 将指针的所有权从一个(非const)unique_ptr 转移给另一个 unique_ptr。

不能拷贝 unique_ptr 的规则有一个例外:我们可以拷贝或赋值一个将要被销毁的 unique_ptr。最常见的例子是从函数返回一个unique_ptr。

2.unique_ptr初始化

1.1 直接初始化

unique_ptr<int> pi; // 可以指向int对象的一个空智能指针(pi == nullptr)
unique_ptr<int> pi(new int(105)); // pi指向一个值为105的int对象
auto pi(new int(105)); // 不能简写为auto,否则推断出是普通指针

1.2 make_unique函数

C++11中没有,C++14才有的。

make_unique 不支持指定删除器。

make_unique 有更高的性能,如果不用删除器,建议优先使用。

unique_ptr<int> p1 = make_unique<int>(100);
auto p2 = make_unique<int>(200);

3.unique_ptr常用操作

3.1 move()

unique_ptr<string> ps1(new string("I Love China!"));
unique_ptr<string> ps2 = move(ps1); // 移动后,ps1为空,ps2指向原来ps1所指

2.3 release()

放弃对指针的控制权,即切断了智能指针和其所指向的对象之间的联系。返回裸指针,将该智能指针置空。

返回的这个裸指针我们可以手动delete来释放,也可以用来初始化另外一个智能指针,或者给另外一个智能指针赋值。

unique_ptr<string> ps1(new string("I Love China!"));
unique_ptr<string> ps2(ps1.release()); // release后,ps1 == nullptr

// 错误:
ps2.release(); // 会导致内存泄漏
unique_ptr<string> ps1(new string("I Love China!"));
unique_ptr<string> ps2(ps1.release()); // release后,ps1 == nullptr

// 正确:
string* p = ps2.release(); // 可以简写为auto p = p2.release();
delete p; // 手动delete释放

2.4 reset()

reset() 不带参数:释放智能指针所指向的对象,并将智能指针置空。

unique_ptr<string> ps1(new string("I Love China1!"));
ps1.reset(); // reset后,ps1 == nullptr

reset() 带参数:释放智能指针所指向的对象,并让该智能指针指向新对象。

unique_ptr<string> ps1(new string("I Love China1!"));
ps1.reset(new string("I Love China2!"));
unique_ptr<string> ps1(new string("I Love China1!"));
unique_ptr<string> ps2(new string("I Love China2!"));
ps1.reset(ps2.release());
// release后,ps2被置空;reset后,释放ps1指向的对象内存,让ps1指向ps2所指向的内存

2.5 nullptr

释放智能指针所指向的对象,并将智能指针置空。

unique_ptr<string> ps1(new string("I Love China!"));
ps1 = nullptr; // 释放ps1所指向的对象,并将ps1置空

2.6 指向一个数组

在这里插入图片描述

2.7 get()

返回智能指针中保存的裸指针。

因为有些第三方库的函数参数需要的是内置裸指针,所以引入该函数。

unique_ptr<string> ps1(new string("I Love China!"));
string *ps = ps1.get();
*ps = "This is a test!";

// 接下来的做法是错误的:
delete ps; // 会产生不可预料的后果

2.8 解引用

解引用:获取该智能指针指向的对象,可以直接操作。

unique_ptr<string> ps1(new string("I Love China!"));
*ps1 = "This is a test!";

unique_ptr<int> pt1(new int(100));
*pt1 = 200;

unique_ptr<int[]> pt2(new int[10]);
//*pt2[0] = 100; // 报错:如果定义的内容是数组,没有解引用运算符

2.9 swap()

交换两个智能指针所指向的对象。

	unique_ptr<string> ps1(new string("I Love China1!"));
	unique_ptr<string> ps2(new string("I Love China2!"));
	std::swap(ps1, ps2);
	unique_ptr<string> ps1(new string("I Love China1!"));
	unique_ptr<string> ps2(new string("I Love China2!"));
	ps1.swap(ps2);

2.10 智能指针名字作为判断条件

	unique_ptr<string> ps1(new string("I Love China1!"));
	if (ps1) { // 等价于if(ps1 != nullptr)
		cout << "ps1不为空" << endl;
	} else {
		cout << "ps1为空" << endl;
	}

	ps1.reset();
	if (ps1) { // 等价于if(ps1 != nullptr)
		cout << "ps1不为空" << endl;
	} else {
		cout << "ps1为空" << endl;
	}

2.11 转换成 shared_ptr 类型

如果 unique_ptr 为右值,就可以将它赋值给 shared_ptr。因为 shared_ptr 包含一个显式构造函数,可用于将右值 unqiue_ptr 转换为 shared_ptr,shared_ptr 将接管原来归 unique_ptr 所拥有的对象。

auto myfunc() {
	return unique_ptr<string>(new string("I Love China")); // 临时对象都是右值
}

int main() {
	
	shared_ptr<string> pss = myfunc(); // 这里系统会为shared_ptr创建控制块

	return 0;
}
unique_ptr<string> ps(new string("I Love China"));
shared_ptr<string> pss = std::move(ps); // 左值转右值。执行后,ps为空,pss就是shared_ptr

返回unique_ptr

虽然 unique_ptr 智能指针不能拷贝,但是,当 unique_ptr 将要被销毁时,是可以拷贝的。最常见的用法就是从函数返回一个 unique_ptr。

unique_ptr<string> tuniqp() {
	return unique_ptr<string>(new string("I Love China!"));
}

int main() {

	unique_ptr<string> ps;
	ps = tuniqp();
	// 可以用ps来接,则临时对象直接构造在ps里。
	// 如果不接,则临时对象会被释放,同时会释放掉所指向的对象的内存。

	return 0;
}

指定删除器

unique_ptr 删除器相对复杂一点,先在类型模板参数中传递进去类型名,然后在参数中给具体的删除器函数名。

void mydeleter(string *pdel) {
	delete pdel;
	pdel = nullptr;
}

typedef void(*fp)(string*); // 定义一个函数指针类型,类型名为fp
unique_ptr<string, fp> ps1(new string("I Love China!"), mydeleter);
using fp2 = void(*)(string *); // 用using定义一个函数指针类型,类型名为fp2
unique_ptr<string, fp2> ps1(new string("I Love China!"), mydeleter);
typedef decltype(mydeleter)* fp3;

// 这里多了一个*,因为decltype返回的是函数类型void(string *)
// 加*表示函数指针类型,现在fp3应该为void *(string *)

unique_ptr<string, fp3> ps1(new string("I Love China!"), mydeleter);
unique_ptr<string, decltype(mydeleter)*> ps4(new string("I Love China!"), mydeleter);
auto mydella = [](string* pdel) {
	delete pdel;
	pdel = nullptr;
};

unique_ptr<string, decltype(mydella)> ps5(new string("I Love China!"), mydella);

两个 shared_ptr 指定的删除器不相同,只要他们所指向的对象相同,那么这两个 shared_ptr 也属于同一个类型。但是 unique_ptr 不一样,指定 unique_ptr 中的删除器会影响 unique_ptr 的类型。

尺寸问题

通常情况下,unique_ptr 尺寸跟裸指针一样。

如果增加了自己的删除器,则 unique_ptr 的尺寸可能增加,也可能不增加。

如果 lambda 表达式作为删除器,尺寸就没变化。

如果定义一个函数作为删除器,尺寸发生变化。

shared_ptr 不管指定什么删除器,shared_ptr 的尺寸(大小)都是裸指针的 2 倍。

	string *p;
	cout << sizeof(p) << endl; // x86: 4字节
	
	unique_ptr<string> ps1(new string("I Love China!"));
	cout << sizeof(ps1) << endl; // x86: 4字节
	auto mydella = [](string* pdel) {
		delete pdel;
		pdel = nullptr;
	};

	unique_ptr<string, decltype(mydella)> ps5(new string("I Love China!"), mydella);

	cout << sizeof(ps5) << endl; // x86: 4字节
typedef void(*fp)(string*); // 定义一个函数指针类型,类型名为fp
unique_ptr<string, fp> ps1(new string("I Love China!"), mydeleter);

cout << sizeof(ps1) << endl; // x86: 8字节

版权声明:本文为qq_42815188原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。