C++ 智能指针

在 C++ 中使用 new 操作来分配和初始化动态对象,并返回一个指向对象的指针。delete 操作符则以此指针为操作数,销毁其指向的对象,并释放其内存。
newdelete的使用不恰当是很容易造成内存泄漏或者非法内存的访问,比如下面的这段代码

1
2
3
4
5
6
void Foo( )
{
int* iPtr = new int[5];
//manipulate the memory block . . .
delete[ ] iPtr;
}

第一眼感觉这段代码似乎没什么问题,内存资源也通过delete操作释放了。但是没有人可以保证,在newdelete之间的代码在执行时不会出现异常提前return而造成没有机会执行到delete操作。
为了使得动态内存易于使用并且更加安全,新标准中提供了两个智能指针来管理动态对象。智能指针的本质是一个RAII类模型,将资源的申请与释放和栈对象的生命周期绑定在一起,当栈对象被销毁的同时会自动释放分配的内存。
shared_ptr允许多个指针指向同一个对象,unique_ptr则拥有其指向的对象,因而是排外的。标准库还定义了weak_ptr表示对shared_ptr管理的对象的弱引用。所有这三个类都定义在memory头文件中。

shared_ptr和unique_ptr共有的操作

  • shared_ptr<T> sp unique_ptr<T> up 指向T类型的对象的空指针;
  • pp用于条件中,如果指向一个对象将返回true;
  • *p 解引用p得到其指向的对象,如果p是空的,结果未定义;
  • p->mem 等同于(*p).mem
  • p.get()返回p对象中保存的对象指针。返回指针所指向的对象可能会被智能指针删除。
  • swap(p,q); p.swap(q) 交换pq中的指针

shared_ptr类

shared_ptr特有的操作

  • make_shared<T>(args); 返回一个T类型对象的智能指针,使用args初始化对象;
  • shared_ptr<T>p(q); pshared_ptr q对象的拷贝,将增加q的引用计数,q中的指针必需可以转换为T*
  • p = q; pq是指向可转换的指针的智能指针shared_ptr;减少p的引用计数,增加q的引用计数;如果p的引用计数为0,删除其指向的对象的内存;
  • p.unique(); 如果p的引用计数是1,则返回true,否则返回false;
  • p.use_count(); 返回p所指对象的引用计数,这是一个很慢的的操作,主要用于调试

shared_ptr是一个RAII的模板类,其定义方式如下

1
2
shared_ptr<int> p1;
shared_ptr<list<int>> p2;

其它的定义和改变shared_ptr的方式:

  • shared_ptr<T> p(new T()); 将智能指针初始化为从 new 返回的指针,shared_ptr将接管这块动态内存
  • shared_ptr<T> p(q); p 将管理由内置指针 q 所指向的对象,q 必须指向由 new 分配的的内存,并且可以转为;
  • shared_ptr<T> p(u); p接管unique_ptr u对象的所有权,并使得u为空指针
  • p.reset() p.reset(q); 如果p是指向对象的唯一指针,reset将会释放p所指的对象。如多提供了额外的内置指针q,将在之后使得p指向q,否则将使p变为空指针

make_shared函数

在使用shared_ptr模板类后最安全分配和使用动态内存的方法就是使用库函数make_shared()。该函数在分配内存并初始化对象后会返回指向该对象的shared_ptr智能指针。make_shared被定义在头文件<memory>,使用方法如下

1
2
3
shared_ptr<int> p1 = make_shared<int>(1);
shared_ptr<string> p2 = make_shared<string>(10, '9');
shared_ptr<int> p3 = make_shared<int>();

shared_ptr如何适当释放动态内存

每个shared_ptr对象都会有一个引用计数器。每当初始化另一个shared_ptr或者在赋值表达式的右边,或作为参数传递给函数,或从函数中作为返回值返回都会让引用计数加一。当给shared_ptr赋予新值时,或者shared_ptr对象本身被销毁时,引用计数都会减一。当引用计数变为0时,shared_ptr的析构函数将销毁其指向的对象,并释放内存。引用计数器的作用就是让shared_ptr可以在适当的时机释放其指向的对象的内存。

unique_ptr

unique_ptr具有其指向对象的所有权,只有一个unique_ptr指向一个对象,其独占所指向的对象;对象将在unique_ptr销毁时被释放。

unique_ptr特有的操作:

  • unique_ptr<T> u1 unique_ptr<T, D>u2 定义两个unique_ptr空指针,它们可以指向类型为T的对象。u1使用delete来释放指针,u2类型使用类型为D的可调用对象delete进行对象释放。
  • unique_ptr<T, D> u(d) 定义unique_ptr空指针,使用类型为 D 的可调用对象 d 进行对象释放;
  • u = nullptr; 删除u所指向的对象,并使其为空指针;
  • u.release(); 交出u所指对象的控制权,返回p所指对象的内置指针,并使得u为空指针;
  • u.reset(); 删除u所指的对象;
  • u.reset(q) u.reset(nullptr) 删除u所指向的对象,并使得u指向内置指针指向的对象,否则u为空指针

weak_ptr

weak_ptr是一个不控制其指向对象的生命周期的智能指针,相反它指向shared_ptr管理的对象。将weak_ptr绑定到shared_ptr并不会改变引用计数。当最后一个shared_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,赋值后w指向与p相同的对象;
  • w.reset() 使得w为空指针;
  • w.use_count() 返回指向同一个对象shared_ptr的个数;
  • w.expired() 如果没有shared_ptr指向对象时返回true,否则返回false;
  • w.lock() 如果已经过期,则返回一个空的shared_ptr,否则返回指向该对象的shared_ptr

使用weak_ptr,可以在不影响对象的生命周期的情况下,安全地访问该对象。

智能指针使用的约定

  • 不要使用相同的内置指针去初始化超过一个智能指针;
  • 不要使用删除 get 函数返回的指针;
  • 不要用 get 函数返回指针去初始化或 reset 别的智能指针;
  • 当使用 get 函数返回的指针时,应当记住当最后一个智能指针销毁时,这个指针会变得无效;
  • 使用智能指针管理资源而不是内存时,记得传递一个删除器(deleter)过去;
-------------本文结束您的阅读与肯定是我持续装*的最大动力-------------