智能指针这个概念经常会碰见,而且面试的时候太经常会被问到了,特来总结一下。
C++11之前的智能指针是auto_ptr,一开始它的出现是为了解决指针没有释放导致的内存泄漏。比如忘了释放或者在释放之前,程序throw出错误,导致没有释放。所以auto_ptr在这个对象声明周期结束之后,自动调用其析构函数释放掉内存。
int t = 3, m =4;
auto_ptr<int> p1(&t);
auto_ptr<const int> p2(&m);
auto_ptr<int> p3(new int[5]); //注意这里一定是[5]而不是(5),因为(5)表示申请了一个里面存着数字5的地址,不要记混了
注意这里只是阐述了怎么用,p1,p2一般不能那么定义,因为一般不用智能指针去指向非堆内存中的地址,因为自行释放非堆地址很有可能出现问题。所以上述程序运行会报错。
相当于如下操作
int t = 3;
int *p = &t;
delete p;
这样是不行的,运行时候会报错。
所以千万不要用一块非new分配的动态内存去初始化一个智能指针。
先说结论就是,
智能指针auto_ptr在被赋值操作的时候,被赋值的取得其所有权,去赋值的丢失其所有权。如【2】中举的例子:
auto_ptr< string> ps (new string ("I reigned lonely as a cloud.");
auto_ptr<string> vocation;
vocaticn = ps;
执行完上面这步之后,ps就不再指向原来的string串了,变成了空串,vocation指向了原来的string串。
但是会出下如下的错误:
auto_ptr<string> films[5] =
{
auto_ptr<string> (new string("Fowl Balls")),
auto_ptr<string> (new string("Duck Walks")),
auto_ptr<string> (new string("Chicken Runs")),
auto_ptr<string> (new string("Turkey Errors")),
auto_ptr<string> (new string("Goose Eggs"))
};
auto_ptr<string> pwin;
pwin = films[2]; // films[2] loses ownership. 将所有权从films[2]转让给pwin,此时films[2]不再引用该字符串从而变成空指针
for(int i = 0; i < 5; ++i)
cout << *films[i] << endl;
以上的程序编译正常,但是运行到输出环节的时候就会出现错误。因为films[2]
此时已经丢掉了控制权。
而如果用unique_ptr
的时候就会在编译期间发现这个错误,因为unique_ptr
是不允许直接赋值的。
比如auto_ptr不能够作为函数的返回值和函数的参数,也不能在容器中保存autp_ptr。
而这些unique_ptr都可以做到。因为C++11之后有了移动语义的存在,这里调用的是移动构造函数。因为移动语义它可以接管原来对象的资源,同时让原来对象的资源置为空。
C++11之后智能指针分为了三种:shared_ptr, unique_ptr, weak_ptr
而weak_ptr相当于shared_ptr的一个辅助指针, 所以正式的智能指针只有shared_ptr和unique_ptr
C++11之后的智能指针的构造函数都有explict关键词修饰,表明它不能被隐式的类型转换。
即如下p1的形式是不行的:
shared_ptr<int> p1 = new int(1024); //这种是不行的,因为等号右边是一个int*的指针,因为有explict修饰,所以它不能被隐式的转换为shared_ptr的类型
shared_ptr<int> p2(new int(1024)); //这种是直接采用了初始化的形式
以下操作是以上两个智能指针都支持的操作
//声明,可以用一个指针显示的初始化,或者声明成一个空指针,可以指向一个类型为T的对象
shared_ptr sp;
unique_ptr up;
//赋值,返回相对应类型的智能指针,指向一个动态分配的T类型对象,并且用args来初始化这个对象
make_shared(args);
make_unique(args); //注意make_unique是C++14之后才有的
//用来做条件判断,如果其指向一个对象,则返回true否则返回false
p;
//解引用
*p;
//获得其保存的指针,一般不要用
p.get();
//交换指针
swap(p,q);
p.swap(q);
举个例子
unique_ptr<int> p1 = make_unique<int>(3);
unique_ptr<int> p2(new int(4));
看上面一定要注意make的相当于一个new了,一定不要用指针去初始化,而是用正常的值去初始化
而下面那种初始化方式需要用一个指针去初始化
不要搞混了
删除器是一个函数
//比如网络资源断开连接
//对于shared_ptr
shared_ptr p (&c,end_connect);
//对于unique_ptr,需要额外定义一个删除器的类型
unique_ptr p(new objT,fcn);
//举个例子如下
unique_ptrdecltype(end_connect)*> p(&c,end_connect)
//这里 用了decltype来获得函数指针类型非常的精妙
//复制构造函数函数
shared_ptrp(q) //会让q的计数器+1
p = q //会让q的计数器+1,同时p原来的计数器-1,如果减为0则自动释放
//引用计数的判断
p.unique() //一个bool函数 如果只有一个引用计数则返回true,如果不是则返回false
p.use_count() //返回与p共享对象的智能指针数量,这个操作可能会比较慢,一般调试的时候用,正式情况下一般不用
//重新赋值
p.reset(new int(1024)) //将p更新为新的指针,同时原来的引用计数-1
//release()用法
//release()返回原来智能指针指向的指针,只负责转移控制权,不负责释放内存,常见的用法
unique_ptr q(p.release()) // 此时p失去了原来的的控制权交由q,同时p指向nullptr
//所以如果单独用:
p.release()
//则会导致p丢了控制权的同时,原来的内存得不到释放
//reset()用法
p.reset() // 释放p原来的对象,并将其置为nullptr,
p = nullptr // 等同于上面一步
p.reset(q) // 注意此处q为一个内置指针,令p释放原来的内存,p新指向这个对象
主意release()只转移控制权,并不释放内存,而reset和=nullptr操作会释放原来的内存
在C++11标准中,还没有make_unique呢,所以在《C++ primer》中说,「与shared_ptr不同,没有类似make_ptr的标准库函数返回一个unique_ptr」但是这个问题在C++14之中就已经解决了。C++14中已经有make_unique了。
std::make_shared是C++11的部分,但是,不幸的是,std::make_unique不是。它是在C++14中才被加入到标准库的。
unique_ptr的两种方式进行赋值,一旦其被赋值之后,不能简单的被替换。
一般情况下
这个是《modern effective C++》21条里说的Item 21: 比起直接使用new优先使用std::make_unique和std::make_shared
原因如下:
std::shared_ptr在实现的时候使用的refcount技术,因此内部会有一个计数器(控制块,用来管理数据)和一个指针,指向数据。因此在执行std::shared_ptr p2(new A)
的时候,首先会申请数据的内存,然后申请内控制块,因此是两次内存申请,而std::make_shared()
则是只执行一次内存申请,将数据和控制块的申请放到一起。
比如一个函数传进去一个智能指针,如果采用如下形式:
fun(shared_ptr(new T), compute());
这个就和编译器有关了,因为函数的参数必须在函数被调用前被估值,所以在调用fun时,会先new
所以执行如下操作:
fun(make_shared() , compute());
不论上面两个参数哪个先执行,都不会导致内存泄漏。make_unique同理
剩下更多的陷阱详见下面这篇博客
必须要注意的 C++ 动态内存资源管理(五)——智能指针陷阱