ChristmasError-Blog

C++智能指针:shared_ptr,unique_ptr,weak_ptr

字数统计: 2.5k阅读时长: 9 min
2018/10/20 Share

动态内存

在C++中,动态内存的管理是通过一对运算符来完成的:newdelete
new:在动态内存中为对象分配空间,并返回一个指向该对象的指针
delete:接受一个动态对象的指针,销毁该对象,并释放与之关联的内存

动态内存的使用需要十分小心,因为要在编程的时候要确保在正确的时间对内存进行释放是极其困难的。如果释放内存不及时,在这种情况下就会出现内存泄漏;但若过早的释放(在仍有指针指向该内存的时候就把其释放了),就会产生引用非法内存的指针(称为空悬指针)。

程序使用动态内存主要处以以下三种原因之一:

  • 程序不知道自己需要多少对象
  • 程序不知道所需对象的准确类型
  • 程序要在多个对象间共享数据(多个对象共享相同底层数据

在C++中,为了能更安全更容易的使用动态内存,新的标准库提供了两种智能指针(smart pointer)类型来管理动态对象(注意:智能指针是类型)。

智能指针

智能指针的行为类似常规指针,但与常规不同的是,它负责自动释放所指向对象
新标准库提供的这两种智能指针的区别在于管理底层指针的方式:
shared_ptr:允许多个指针指向同一个对象
imoqie_ptr:“独占”所指向的对象
同时,标准库还提供了一个名为weak_ptr的伴随类:
weak_ptr:它是一种弱引用,指向shared_ptr所管理的对象。

以上三种类型都定义在memory头文件中。

shared_ptr类

shared_ptr类类似vector,智能指针也是一种模板(template)。因此在创建智能指针对象的时候,也必须提供指针指向的类型的信息。

1
2
3
4
shared_ptr<int> p1;
shared_ptr<string> p2;
shared_ptr<int> p3 (new int(16)); //正确
shared_ptr<int> p4 = new int(16); //错误

对于p3,p4,shared_ptr的构造函数是explicit的,因此我们不能将一个内置指针隐式转换为一个智能指针(p4的初始化隐式地要求编译器用一个new返回的int*来创建一个shared_ptr,因此是错误的)。

shared_ptr和unique_ptr都支持的操作
shared_ptr< T >sp 空智能指针,默认初始化的智能指针中保存着一个空指针
p 将p作为一个条件判断,若p指向一个对象,则为true
*p 解引用p,获得p指向的对象
p->mem 等价于(*p).mem
p.get() 返回p中保存的指针。小心使用,若智能指针释放了其对象,返回的指针所指向的对象也同时消失了。
swap(p,q) 交换p和q的指针
p.swap(q) 交换p和q的指针
shared_ptr独有的操作
make_shared< T >(args) 返回一个shared_ptr,指向一个动态分配的类型为T的对象,使用args初始化该对象
shared_ptr< T >p(q) p是shared_ptr q的拷贝,此操作会递增q中的计数器。q指针必须能转换为T*
p = q p和都为shared_ptr,保存的指针必须能相互转换,此操作会递减p的引用计数,递增q的引用计数;若p的引用计数为0,则将其管理的内存释放
p.unique() 若p.use_count()为1,返回true,否则false
p.use_count() 返回与p共享对象的智能指针数量(只要勇调试)
定义和改变shared_ptr的其他方法
shared_ptr< T > p(q) p管理内置指针q所指向的对象,q必须质量new分配的内存切可以转换为T*内型
shared_ptr< T > p(p2,d) p是p2的拷贝,唯一区别是p将使用可调用对象d来代替delete(lambda表达式)
shared_ptr< T > p(u) p从unique_ptr u处接管了对象所有权,将u置为空
shared_ptr< T > p(q,d) p将使用可调用对象d来代替delete(lambda表达式)
p.reset() p若是唯一指向器对象的shared_ptr,reset将释放此对象
p.reset(q) 若释放后,传递了q,会令p指向q
p.reset(q,d) 结合以上两种方法,使用lambda表达式用d代替delete

对于shared_ptr< T >p(q)的操作 和 p=q的拷贝和赋值操作,我们要注意个对象中的计数器的变化和引用计数(reference count)的变化。

我们用一个shared_ptr去初始化另一个shared_ptr,或将它作为参数传递给一个函数以及作为函数的返回值,它所关联的计数器就会递增。
若給shared_ptr赋予一个新值或者是shared_ptr被销毁,一个局部的shared_ptr离开了作用域,计数器就会递减。

make_shared 函数

make_shared同样也包含在memory头文件中。
最安全的分配和使用动态内存的方法是调用一个名为make_shared 的标准库函数。
make_shared函数在动态内存中分配一个对象并初始化它,返回指向此对象的shared_ptr。

1
2
3
4
5
//p1为一个指向值为999的int的shared_ptr
shared_ptr<int>p1 = make_shared<int>(999);

//p2为指向一个值初始化的int(即此int值为0)
shared_ptr<int>p2 = make_shared<int>();

p2看出,如果传递任何参数,对象就会进行值初始化

shared_ptr自动销毁所管理的对象,自动释放相关联的内存

当指向一个对象的最后一个shared_ptr被销毁,shared_ptr类会自动销毁这个对象。他通过他的析构函数完成这项工作。
若当动态对象不被使用,shared_ptr类会自动释放动态对象,这个特性就是的动态内存的使用变得十分容易。
例如一下的factory 函数:

1
2
3
4
5
6
shared_ptr<FOO> factory (T arg)
{
//函数内容
//shared_ptr负责释放内存
return make_shared<FOO>(arg);
}

我们在use_factory函数调用它:

1
2
3
4
5
6
void use_factory(T arg)
{
shared_ptr<FOO>p = factory(arg);
//函数内容
}
//在这里,p属于局部的变量,p离开了作用域,它指向的内存也会被自动释放

p属于局部的变量,p离开了作用域,它指向的内存也会被自动释放。当p被销毁时,将递减引用计数检查他是否为0
但如果有其他的shared_ptr也同时指向这块内存,他将不会被释放(引用计数器检查不为0)

1
2
3
4
5
6
7
8
9
void use_factory(T arg)
{
shared_ptr<FOO>p = factory(arg);
//(此例此处可以简单的看作引用计数为1)
//函数内容
return p;
//函数反悔了p,引用计数进行了递增操作(此处可看作引用计数为2)
}
//p离开了作用域,引用计数递减但最终但不为0(引用计数为1)

和其特性一样,若引用计数不为0,他的内存都不会被释放,因此保证shared_ptr在无用之后不再保留非常重要(避免内存浪费)。
一种要注意的情况就是,如果将shared_ptr保存在一个容器中(如vector),随后重拍了容器,不再需要其中某些元素,这种情况下,要确保用erase函数删除那些不再需要的shared_ptr。

unique_ptr类

unique_ptr“拥有”它所指向的对象,与shared_ptr不同,某个时刻只能由一个unique_ptr指向一个给定对象。
和shared_ptr不同的是,没有类似make_shared的标准库函数返回一个unique_str。
我们要定义一个unique_ptr时,需要将其绑定到一个new返回的指针上,类似shared_ptr,初始化unique_ptr必须采用直接初始化形式(unique_ptr构造函数也为explicit)。

1
2
3
4
5
6
unique_ptr<string> p1(new string ("ABC");
unique_ptr<string> p2(p1); //错误,unique_ptr不支持拷贝
unique_ptr<string> p2(p1.release()); //正确,up1用realease置为空,p2初始化为p1原来的指针
unique_ptr<string> p3;
p3 = p2; //错误,unique_ptr不支持赋值
p2.reset(p1.release()); //正确,reset释放了p2指向的内存,这行代码表示将所有权从p1转移给p2
unique_ptr操作(与shared_ptr相同操作不重复列出)
unique_ptr< T >u1 空unique_ptr,指向类型为T对象。
unique_ptr< T , D> u2 空unique_ptr,与u1不同的是,u2会使用一个类型为D的可调用对象来释放他的指针
unique_ptr< T , D > u(d) 空unique_ptr,会使用类型为D的对象d来代替delete
u = nullptr 释放u所指向对象,u置为空
u.realease() 放弃对指针控制权,返回指针并将u置为空
u.reset() 释放u所指向的对象
u.reset(q) 若提供了内置指针q,会令u指向q
u.reset(nullptr)

weak_ptr类

weak_ptr是一种不控制所指向对象生存期的正能指针,他指向一个shared_ptr管理的对象。
将一个weak_ptr绑定到一个shared_ptr不会改变目标对象的引用计数。
一旦最后一个指向该对象的shared_ptr被销毁,对象就会被释放,即使有weak_ptr指向该对象,他依旧会被释放。
因此,weak_ptr有智能指针“弱”共享的特点。

weak_ptr
weak_ptr< T > w 空weak_ptr可以指向类型为T的对象
weak_ptr< T >w(sp) 与shared_ptr sp 指向相同对象的weak_ptr,T必须能转换为sp所指向的类型
w = p p可以是一个shared_ptr也可以是weak_ptr,赋值后p与w共享对象
w.rese() w置为空
w.use_count() 与w共享对象的shared_ptr的数量
w.expired() 若w.use_count()为0,返回true,否则false
w.lock() 如果expired为true,返回一个空shared_ptr,否则返回一个指向w的对象的shared_ptr
1
2
3
auto p =  make_shared<int>(666);
weak_ptr<int> wp(p);
//wp弱共享p,不会改变p引用计数

在使用weak_ptr时,由于指向的对象可能不存在,因此需要调用lock来检查weak_ptr所指向对象是否存在,如:

1
2
3
4
if(shared_ptr<int> np== wp.lock()){  //lock在指向对象存在时返回一个指向该对象的shared_ptr
//在if中,np与wp共享对象
//但仅仅可以保证在此if中,该对象的共享访问是安全的
}
CATALOG
  1. 1. 动态内存
  2. 2. 智能指针
    1. 2.1. shared_ptr类
      1. 2.1.1. make_shared 函数
      2. 2.1.2. shared_ptr自动销毁所管理的对象,自动释放相关联的内存
    2. 2.2. unique_ptr类
    3. 2.3. weak_ptr类