C++智能指针的分析与使用

发表于:2018-1-12 10:01

字体: | 上一篇 | 下一篇 | 我要投稿

 作者:Tanswer_    来源:CSDN博客

#
DoNet
分享:
  手动管理的弊端
  在简单的程序中,我们不大可能忘记释放 new 出来的指针,但是随着程序规模的增大,我们忘了 delete 的概率也随之增大。在 C++ 中 new 出来的指针,赋值意味着引用的传递,当赋值运算符同时展现出“值拷贝”和“引用传递”两种截然不同的语义时,就很容易导致“内存泄漏”。
  手动管理内存带来的更严重的问题是,内存究竟要由谁来分配和释放呢?指针的赋值将同一对象的引用散播到程序各处,但是该对象的释放却只能发生一次。当在代码中用完了一个资源指针,该不该释放 delete 掉它?这个资源极有可能同时被多个对象拥有着,而这些对象中的任何一个都有可能在之后使用该资源,其余指向这个对象的指针就变成了“野指针”;那如果不 delete 呢?也许你就是这个资源指针的唯一使用者,如果你用完不 delete,内存就泄漏了。
  资源的拥有者是系统,当我们需要时便向系统申请资源,当我们不需要时就让系统自己收回去(Garbage Collection)。当我们自己处理的时候,就容易出现各种各样的问题。
  C++ 中的智能指针
  为了让用户免去手动 delete 资源的烦恼,不少类库采用了 RAII 风格,即 Resource Acquisition Is Initialization(资源获取即初始化)。这种风格采用类来封装资源,在类的构造函数中获取资源,在类的析构中释放资源,这个资源可以是内存,可以是一个网络连接。
  智能指针就是 C++ RAII 的一种应用,是存储指向动态分配对象指针的类。智能指针在面对异常的时候格外有用,因为它们能在适当的时间删除所指向的对象(可以了解一下异常安全)。C++ 中的智能指针首先出现在 boost 中,随着使用的人越来越多,C++11 也已经引入了智能指针来管理动态对象。C++11 主要提供了 shared_ptr、unique_ptr、weak_ptr 三种不同类型的智能指针。
  shared_ptr
  智能指针是(几乎总是)模板类,shared_ptr 同样是模板类,所以在创建 shared_ptr 时需要指定其指向的类型。shared_ptr 负责在不使用实例时释放由它管理的对象,同时它可以自由的共享它指向的对象。
  shared_ptr 使用经典的“引用计数”的方法来管理对象资源。引用计数指的是,所有管理同一个裸指针( raw pointer )的 shared_ptr,都共享一个引用计数器,每当一个 shared_ptr 被赋值(或拷贝构造)给其它 shared_ptr 时,这个共享的引用计数器就加1,当一个 shared_ptr 析构或者被用于管理其它裸指针时,这个引用计数器就减1,如果此时发现引用计数器为0,那么说明它是管理这个指针的最后一个 shared_ptr 了,于是我们释放指针指向的资源。
  在底层实现中,这个引用计数器保存在某个内部类型里(这个类型中还包含了 deleter,它控制了指针的释放策略,默认情况下就是普通的delete操作),而这个内部类型对象在 shared_ptr 第一次构造时以指针的形式保存在 shared_ptr 中(所以一个智能指针的析构会影响到其他指向同一位置的智能指针)。shared_ptr 重载了赋值运算符,在赋值和拷贝构造另一个 shared_ptr 时,这个指针被另一个 shared_ptr 共享。在引用计数归零时,这个内部类型指针与 shared_ptr 管理的资源一起被释放。此外,为了保证线程安全性,引用计数器的加1,减1操作都是原子操作,它保证 shared_ptr 由多个线程共享时不会爆掉。
  对于 shared_ptr 在拷贝和赋值时的行为,《C++Primer第五版》中有详细的描述:
  每个 shared_ptr 都有一个关联的计数值,通常称为引用计数。无论何时我们拷贝一个 shared_ptr,计数器都会递增。
  例如,当用一个 shared_ptr 初始化另一个 shred_ptr,或将它当做参数传递给一个函数以及作为函数的返回值时,它所关联的计数器就会递增。当我们给 shared_ptr 赋予一个新值或是 shared_ptr 被销毁(例如一个局部的 shared_ptr 离开其作用域)时,计数器就会递减。一旦一个 shared_ptr 的计数器变为0,它就会自动释放自己所管理的对象。
  下面看一个常见用法,包括:
  1. 创建 shared_ptr 实例 2. 访问所指对象 3. 拷贝和赋值操作 4. 检查引用计数。更多的用法请自行查阅。
/*
* @filename:    shared_ptr.cpp
* @author:      Tanswer
* @date:        2018年01月10日 19:40:45
* @description:
*/
#include <iostream>
#include <string>
#include <memory>
using namespace std;
class Test{
public:
Test(string name){
name_ = name;
cout << this->name_ << "  constructor" << endl;
}
~Test(){
cout << this->name_ << "  destructor" << endl;
}
string name_;
};
int main()
{
/* 类对象 原生指针构造 */
shared_ptr<Test> pStr1(new Test("object"));
cout << (*pStr1).name_ << endl;
/* use_count()检查引用计数 */
cout << "pStr1 引用计数:" << pStr1.use_count() << endl;
shared_ptr<Test> pStr2 = pStr1;
cout << (*pStr2).name_ << endl;
cout << "pStr1 引用计数:" << pStr1.use_count() << endl;
cout << "pStr2 引用计数:" << pStr2.use_count() << endl;
/* 最安全高效的方法,make_shared 库函数 */
shared_ptr<Test> p = make_shared<Test>("pppppp");
shared_ptr<Test> q = make_shared<Test>("qqqqqq");
cout << "p = q 语句执行前 p: "<< (*p).name_ << " q: "<< (*q).name_ << endl;
/* 执行 p = q 这样的赋值操作,会递减p的引用计数值,递增q的引用计数值 */
p = q;  //此后p 和 p 指向相同对象
cout << "p = q 语句执行后 p: "<< (*p).name_ << " q: "<< (*q).name_ << endl;
cout << "p 引用计数:" << p.use_count() << endl;
cout << "p 引用计数:" << p.use_count() << endl;
/* 先new 一个对象,把原始指针传递给shared_ptr的构造函数 */
int *pInt1 = new int(11);
shared_ptr<int> pInt2(pInt1);
/* unique()来检查某个shared_ptr 是否是原始指针唯一拥有者 */
cout << pInt2.unique() << endl; //true 1
/* 用一个shared_ptr对象来初始化另一个shared_ptr实例 */
shared_ptr<int> pInt3(pInt2);
cout << pInt2.unique() << endl; //false 0
return 0;
}
  错误用法一:循环引用
  循环引用可以说是引用计数策略最大的缺点,“循环引用”简单来说就是:两个对象互相使用一个 shared_ptr 成员变量指向对方(你中有我,我中有你)。突然想到一个问题:垃圾回收器是如何处理循环引用的? 下面看一个例子:
/*
* @filename:    循环引用.cpp
* @author:      Tanswer
* @date:        2018年01月10日 22:43:45
* @description:
*/
#include <iostream>
#include <string>
#include <memory>
using namespace std;
class Children;
class Parent{
public:
~Parent(){
cout << "Parent    destructor" << endl;
}
shared_ptr<Children> children;
};
class Children{
public:
~Children(){
cout << "Children  destructor" << endl;
}
shared_ptr<Parent> parent;
};
void Test()
{
shared_ptr<Parent> pParent(new Parent());
shared_ptr<Children> pChildren(new Children());
if(pParent && pChildren){
pParent -> children = pChildren;
pChildren -> parent = pParent;
}
cout << "pParent use_count: " << pParent.use_count() << endl;
cout << "pChildren use_count: " << pChildren.use_count() << endl;
}
int main()
{
Test();
return 0;
}
  输出结果如下:
 
  退出之前,它们的 use_count() 都为2,退出了 Test() 后,由于 pParent 和 pChildren 对象互相引用,它们的引用计数都是 1,不能自动释放(可以看到没有调用析构函数),并且此时这两个对象再无法访问到。这就引起了c++中那臭名昭著的“内存泄漏”。

上文内容不用于商业目的,如涉及知识产权问题,请权利人联系博为峰小编(021-64471599-8017),我们将立即处理。
21/212>
精选软件测试好文,快来阅读吧~

关注51Testing

联系我们

快捷面板 站点地图 联系我们 广告服务 关于我们 站长统计 发展历程

法律顾问:上海兰迪律师事务所 项棋律师
版权所有 上海博为峰软件技术股份有限公司 Copyright©51testing.com 2003-2024
投诉及意见反馈:webmaster@51testing.com; 业务联系:service@51testing.com 021-64471599-8017

沪ICP备05003035号

沪公网安备 31010102002173号