引用

c++ 11:

  • std::unique_ptr

c++ 14:

  • std::make_unique

前言

正常的指针如果是通过new或者malloc方式还需要额外的手动释放,delect或free。

智能指针的作用是防止忘记调用delete释放内存和程序异常的进入catch块忘记释放内存。另外指针的释放时机也是非常有考究的,多次释放同一个指针会造成程序崩溃,这些都可以通过智能指针来解决。

智能指针主要用于管理在堆上分配的内存,它将普通的指针封装为一个栈对象。当栈对象的生存周期结束后,会在析构函数中释放掉申请的内存,从而防止内存泄漏。
智能指针的作用是管理一个指针,因为存在以下这种情况:申请的空间在函数结束时忘记释放,造成内存泄漏。使用智能指针可以很大程度上的避免这个问题,因为智能指针是一个类,当超出了类的实例对象的作用域时,会自动调用对象的析构函数,析构函数会自动释放资源。所以智能指针的作用原理就是在函数结束时自动释放内存空间,不需要手动释放内存空间。

声明方式:std::unique_ptr<type> typename{},
比如std::unique_ptr<int> ptra{std::make_unique<int> (150)}

转到定义看到:
能看到是用class写的,配合上模板。


正文

智能指针之所以智能,肯定会与传统指针有区别。

no1 []

比如说,传统指针无论大小,都可以用[]方式访问,但是智能指针就不行了。

1
2
3
4
5
6
7
8
9
10
int main(){

int *p = new int;
std::unique_ptr<int> intP{ new int{ 5 } };

std::cout << p[0] << std::endl;
//std::cout << intP[0] << std::endl; 这一句会报错,编译器给的说明就是没有符合的[]运算符操作

return 0;
}

当然很大程度上单个int不被认作为’数组‘。
传统指针需要通过int *p = new int[5],这样才能严格算一个数组。
那么智能指针也不例外,需要用到[],即std::unique_ptr<int[]> intP{new int[]{0}};

能够看到是合法有效的。


no2 =

传统指针之间可以套娃,多个指针指向一片内存区域

1
2
3
4
5
6
7
8
int *a = new int;
int *p = a;
int *p2 = a;
int *p3 = a;
std::cout << a << std::endl;
std::cout << p << std::endl;
std::cout << p2 << std::endl;
std::cout << p3 << std::endl;

可以看到指针a由我们人为new了一块内存地址,紧接着其他指针都指向指针a的地址,但是如果a被释放了,那么其他指针就有可能成为野指针,是一种非常不妙的情况。除非特殊需求,一般也是会尽量避免这种写法。

而智能指针则不允许多个只能指针指向一块区域(简单说就是智能指针a不能赋值给智能指针b)

1
2
std::unique_ptr<int> intP{ new int };
std::unique_ptr<int> intP2 = intP;

编译器自然而然的给出了错误。

但是有一说一,强转还是很骚的,比如说:

1
2
3
int *a = new int;
std::unique_ptr<int> intP = (std::unique_ptr<int>) &a;
std::unique_ptr<int> intP2 = (std::unique_ptr<int>)&a;

通过这种强转的方法,编译器也没有给出错误和警告。
当然这种假象让两个智能指针指向同一块内存也就是图一乐。
我们能想到的委员会也早想到了,所以每个版本的特性都特别重要。


no3 c++14初始化

c++14之后,在初始化的时候有新增了一种方式
std::unique_ptr<int> intP { std::make_unique<int>() }
std::unique_ptr<int[]> intP { std::make_unique<int[]>() }

二者区别在于第一种方式初始化时()代表了指针指向区域的初始化值为多少
第二种则是初始化通过()来声明有几个成员。

它的好处目前也没能感知到,百度看了几个大致说法就是更安全更简单,至于不能使用定制删除器和不能完美传递一个initializer_list都是目前没感知的。得等以后深入用到了在挖了。


rest()

reset能够释放智能指针的内存空间,并将其置为nullptr;

1
2
3
4
std::unique_ptr<int> intP{ new int{0} };
intP.reset();

std::cout << intP << std::endl;

内存地址置空跟变成全0意思相同,就是说没有地址了,申请的内存也还给系统了。


get()

一开始使用智能指针的时候会感觉碍手碍脚,不像传统指针能通过*p = 直接改变值的过程
所以get方法可以让一个指针指向智能指针指向的内存区域。

1
2
3
4
5
6
int *p;
std::unique_ptr<int> intP{ new int };
p = intP.get();

std::cout << intP << std::endl;
std::cout << p << std::endl;

可以看到指针p也指向了智能指针指向的区域

然后进行修改:

1
2
3
4
5
6
7
8
9
int *p;
std::unique_ptr<int> intP{ new int{0} };
p = intP.get();

std::cout << *intP << std::endl;
std::cout << *p << std::endl;
*p = 500;
std::cout << *intP << std::endl;
std::cout << *p << std::endl;

先初始化了智能指针的值为0,p在指向智能指针
然后通过*p=500也是成功修改了智能指针的值。


release()

release会返回unique_ptr的指针将其置为nullptr,但是release不会释放占用的内存空间。

1
2
3
std::unique_ptr<int> intP{ new int{0} };
intP.release();
std::cout << intP << std::endl;

虽然release将智能指针的地址置为nullptr了,但是我们说过,原先的内存没有释放掉,而且release会返回原先智能指针的地址

1
2
3
4
5
6
7
int *p;
std::unique_ptr<int> intP{ new int{0} };

std::cout << intP << std::endl;
p = intP.release();
std::cout << intP << std::endl;
std::cout << p << std::endl;

这样就可以看出没release之前intP的地址和release之后的结果。包括p接收了release返回的地址。


转移

智能指针具有唯一性是上述提到过的。所以智能指针不能够被复制,但是有转移的方法。

1
2
3
4
5
6
7
8
9
10
std::unique_ptr<int> intP{ new int{0} };
std::unique_ptr<int> intP2{};

std::cout << intP << std::endl;
std::cout << intP2 << std::endl;

intP2 = std::move(intP);

std::cout << intP << std::endl;
std::cout << intP2 << std::endl;

看到intP被转移后地址被置为nullptr,intP2则获取了intP原先的地址。


结语

这玩意目前还不知道应用场景在哪,毕竟不太习惯。