智能指针的目的:
解决内存泄漏和指针悬挂。
内存泄漏:
不再使用的内存没有得到释放。
悬挂指针:
悬挂指针也叫野指针,是未初始化或未清零的指针。与空指针(NULL)不同,悬挂指针无法通过简单地判断是否为 NULL避免,而只能通过养成良好的编程习惯来尽力减少。
悬挂指针的成因主要有两种:
- 指针变量没有被初始化,任何指针变量刚被创建时不会自动成为NULL指针,它的缺省值是随机的,它会乱指一气。所以,指针变量在创建的同时应当被初始化,要么将指针设置为NULL,要么让它指向合法的内存。
- 指针p被free或者delete之后,没有置为NULL,让人误以为p是个合法的指针。 它们只是把指针所指的内存给释放掉,但并没有把指针本身干掉。
智能指针auto_ptr(只能指向动态内存)(也是unique_ptr的一种)
auto_ptr是c++标准库的类模板,auto_ptr对象通过初始化指向new创建的动态内存,auto_ptr对象即为这块内存的拥有者,一块内存不能有两个拥有者。(C++98标准)
当auto_ptr对象的生命周期结束时,析构函数会将auto_ptr对象拥有的动态内存自动释放,防止内存泄漏。
#include <iostream>
#include <memory>
using namespace std;
class Test
{
public:
Test(int a = 0 )
: m_a(a)
{}
~Test( )
{
cout<<"Calling destructor"<<endl;
}
public:
int m_a;
};
int main()
{
auto_ptr<Test> t1(new Test(3));
cout << t1->m_a <<endl;
return 0;
}
使用方法:
初始化:
1.构造函数
1>.直接:auto_ptr<int> api(new int(22));
2>.用普通指针构造: int *p=new int(22); auto_ptr<int> api(p);
2.拷贝构造:用已存在的智能指针构造新的智能指针:
auto_ptr<string> ap_string(new string("keke"));
auto+ptr<string> ap_stringnew(ap_string);
//构造或赋值会造成拥有权转移,ap_string失去对keke的所有权,由ap_stringnew获得,
对象销毁时,ap_stringnew负责内存的自动销毁。
3.赋值
auto_ptr<int> p1(new int(1024));
auto_ptr<int> p2(new int(2048));
p1=p2;
//赋值前,p1指向对象被删除,赋值后,p2不再指向该对象,p1拥有2048这块内存的控制权
//这就有问题了
1、base1.get():返回当前指针对象;
2、base1.release():清空当前智能指针对象,并返回类型指针。所以假如我们要正常删除,那么需要这样:
Base1*base2 = base1.release();
delete base2;
3、base1.reset():是重置智能指针,即把内存删除,且智能指针指向空,但类型不变,所以可以这样安全便捷地删除:
auto_ptr的问题:
- auto_ptr将所有权转让给另一个auto_ptr时。在函数之间传递auto_ptr时,这确实是个问题。例如,我在Foo()中有一个auto_ptr,这个指针从Foo中传递了另一个函数Fun()。现在一旦Fun()完成执行,所有权就不会返回给Foo()。
- 多个auto_ptr不能同时拥有同一个对象,
- 例如:Test *t1 = new Test(3); auto_ptr<Test> ptr1(t1); auto_ptr<Test> ptr2(t1);
- ptr1与ptr2都认为指针t1是归它管的,在析构时都试图删除t1,这样就造成了重复释放问题。
- 不能用auto_ptr管理数组指针,auto_ptr的析构函数中删除指针用的是delete,而不是delete []。
- auto_ptr不可做为容器(vector, list, map)元素,因为进行值传递时,当从函数返回时,容器中的元素会被置位为NULL。
智能指针unique_ptr:独占所有权,防拷贝
和auto_ptr有区别,控制权唯一,不能随意转换。用法都差不多。实现原理是防止对象间的拷贝与赋值。具体实现是将拷贝构造函数和赋值运算符重载函数设置为保护或私有,并且只声明不实现,并将标志设置为保护或私有,防止他人在类外拷贝,简单粗暴,但是也提高了代码的安全性。
unique_ptr指针本身的生命周期:从unique_ptr指针创建时开始,直到离开作用域。离开作用域时,若其指向对象,则将其所指对象销毁(默认使用delete操作符,用户可指定其他操作)。
unique_ptr指针与其所指对象的关系:在智能指针生命周期内,可以改变智能指针所指对象,如创建智能指针时通过构造函数指定、通过reset方法重新指定、通过release方法释放所有权、通过移动语义转移所有权。
常规用法:
unique_ptr类中有get()、reset()、release()等函数。
get(): 获得原生指针,返回当前指针对象
reset():重置,显式释放资源
reset(new XX):重置,重新指定对象
release():释放所有权到某一原生指针上
另外可以通过std::move将所有权由一个unique_ptr对象转移到另一个unique_ptr对象上,
unique_ptr<int> p1(new int(10));
unique_ptr<int> p2(new int(48));
p1.reset(); // 释放p1指向的内存, p1置空
int *pt = p2.release(); // p2放弃对指针的控制权,返回一个普通指针,并将p2置成空。
p1.reset(pt); // p1获得pt的控制权,如果p1对其它指针有控制权,那么就放弃原来的控制权。
p2 = unique_ptr<int>(new int(21));
p1 = nullptr;
// same as:
// p1.reset();
p2.reset(nullptr);
// same as:
// p2 = nullptr;
#include <iostream>
#include <memory>
using namespace std;
int main()
{
//1. unique_ptr的创建
//1.1)创建空的,然后利用reset指定对象
unique_ptr<int> up1;
up1.reset(new int(3));
//1.2)通过构造函数在创建时指定动态对象
unique_ptr<int> up2(new int(4));
//2. 获得原生指针(Getting raw pointer )
int* p = up1.get();
//3.所有权的变化
//3.1)释放所有权,执行后变为empty
int *p1 = up1.release();
//3.2)转移所有权,执行后变为empty
unique_ptr<int> up3 = std::move(up2);
//4.显式释放资源
up3.reset();
return 0;
}
unique_ptr禁止赋值和复制,“唯一”地拥有其所指对象,同一时刻只能有一个unique_ptr实例指向给定对象。也就是说模板类unique_ptr的copy构造函数以及等号(“=”)操作符是无法使用的。
#include <iostream>
#include <memory>
using namespace std;
void Fun1( unique_ptr<int> up )
{
}
int main()
{
unique_ptr<int> up1 = unique_ptr<int>(new int(10));
//不允许复制(Copy construction is not allowed),所以以下三个均错误
unique_ptr<int> up2 = up1; // error
unique_ptr<int> up3(up1); // error
Fun1(up1); // error
//不允许赋值('='),所以下面错误
unique_ptr<int> up4;
up4 = up1; // error
return 0;
}
针对auto_ptr缺陷的改善:
- 管理数组指针:因为unique_ptr有unique_ptr< X[ ] >重载版本,销毁动态对象时调用delete[],所以可以用unique_ptr来管理数组指针。
unique_ptr< Test[ ] > uptr1(new Test[3]);
//注意 unique_ptr<Test> uptr3(new Test[3]);是不对的
unique_ptr<int[]> uptr2(new int[5]);
- 容器(vector, list, map)元素
智能指针shared_ptr:
利用引用计数;每有一个指针指向相同的一片内存时,引用计数+1,每当一个指针取消指向一片内存时,引用计数-1, 减 为0时释放内存。
常见操作:
shared_ptr类中有get()、reset()、unique()、swap()等函数。
get(): 获得原生指针
reset():重置,显式释放资源
reset(new XX):重新指定对象
unique ():检测对象管理者是否只有一个shared_ptr实例
在shared_ptr的RAII实现机制中,默认使用delete实现资源释放,也可以定义自己的函数来释放,比如当其管理数组指针时,需要delete[],这时就需要自定义释放函数。自定义释放的方法有两种:lambda表达式和括号操作符的重载
问题:
- 在多线程环境下,引用计数的更新存在安全隐患
- 第一个比较容易理解,我们可以在改变引用计数的时候加上一把互斥锁,防止多线程带来的隐患。
- 循环引用问题
-
原因:加了这两句代码后,这两个节点的引用计数都增加了1。出了作用域进行析构时,两个对象均不能释放,因为prev的要释放的前提是next释放,而next的释放又依赖于prev的释放。最后就形成了循环引用,谁都释放不了。2
3
4
5
6
7
void
TestSharedPtr()
{
shared_ptr<Node> cur(
new
(Node));
shared_ptr<Node> next(
new
(Node));
cur->_next = next;
// 1
next->_prev = cur;
// 2
-
解决办法:
template<class T>
struct Node
{
public:
~Node()
{
cout << "delete:" << this << endl;
}
public:
weak_ptr<Node> _prev;
weak_ptr<Node> _next;
};
void TestWeakPtr()
{
shared_ptr<Node> cur(new Node());
shared_ptr<Node> next(new Node());
cout << "连接前:" << endl;
cout << "cur:" << cur.use_count() << endl;
cout << "next:" << next.use_count() << endl;
cur->_next = next;
next->_prev = cur;
cout << "连接后:" << endl;
cout << "cur:" << cur.use_count() << endl;
cout << "next:" << next.use_count() << endl;
}
因为weak_ptr(弱引用智能指针)会对引用计数会做特殊处理(上述情况不加1)。
- 定制删除器
- 在shared_ptr中只能处理释放new开辟的空间,而对于malloc,以及fopen打开的文件指针不能处理,所以提出了定制删除器,方便释放其他类型的指针,而其实现则是通过仿函数(通过重载operator())来实现。
智能指针weak_ptr:
weak_ptr类中有use_count()、expired()等函数。
use_count():观测资源的引用计数
expired():等价于use_count()==0,但更快
lock():获取shared_ptr,当expired()==true的时候,lock()函数将返回一个存储空指针的shared_ptr
总结:
auto_ptr 管理权的转移 ->不建议使用
unique_ptr 防拷贝 ->简单粗暴
shared_ptr 引用计数 ->增减引用计数,直到对象的引用计数为0时再释放
week_ptr 弱指针 ->辅助shared_ptr解决循环引用的问题。
本文转载总结的博客原文:
https://blog.csdn.net/zhou753099943/article/details/52412381
https://blog.csdn.net/shuishanga/article/details/52982102