智能指针

为什么要设计智能指针?

首先给出一个例子

void Fun(string & str)
{
   string *ps =new string (str);
   if(weird_thing())
      {
         throw exception();
      }
    str =*ps;
    delete ps;
   return str;
}

当抛出异常的时候,delete将不会被执行,这样就造成了内存泄漏的问题。有的人也许会说,我们可以将很容易避免这类问题啊?

直接在throw exception();之前delete 不就行了,是的并应该这么做。但是很多人都容易忘记写,事实上很多人连最后一句delete或许都会忘记。

 那有没有什么方法,可以让当Fun这样的函数终止(不管是正常终止,还是由于出现了异常而终止),本地变量都将自动从栈内存中删除—因此指针ps占据的内存将被释放,如果ps指向的内存也被自动释放,那岂不是美哉?

  我们知道析构函数可以在函数结束时释放内存,那么如果ps指针有一个析构函数,该析构函数在ps过期时,自动释放掉对于的内存。但ps只是一个常规指针,他并不具备这样的功能。

   所以智能指针背后的思想就是:将基本类型指针封装为类对象指针(这个类肯定是个模板,以适应不同基本类型的需求),并在析构函数里编写delete语句删除指针指向的内存空间。

智能指针解决的问题

 动态内存管理经常会出现两种问题,一种是忘记释放内存造成内存泄漏,另外一种是,一种是尚有指针引用内存的情况下就释放了它,就会产生引用非法内存的指针。

 为此STL 一共给我们提供了四种智能指针:auto_ptr、unique_ptr、shared_ptr 和 weak_ptr,auto_ptr 是 C++98 提供的解决方案,C+11 已将其摒弃,并提出了 unique_ptr 作为 auto_ptr 替代方案。虽然 auto_ptr 已被摒弃,但在实际项目中仍可使用,但建议使用较新的 unique_ptr,因为 unique_ptr 比 auto_ptr 更加安全,后文会详细叙述。shared_ptr 和 weak_ptr 则是 C+11 从准标准库Boost中引入的两种智能指针。标准库提供的两种智能指针的区别在于管理底层指针的方法不同,shared_ptr允许多个指针指向同一个对象,unique_ptr则“独占”所指向的对象。标准库还定义了一种名为weak_ptr的伴随类,它是一种弱引用,指向shared_ptr所管理的对象,这三种智能指针都定义在memory头文件中。

Shared_ptr

 make_shared函数

最安全的使用和分配动态内存的方法是调用一个make_shared的标准库函数,此函数在动态内存中分配一个对象并初始化他,返回指向此对象的shared_ptr;

shared_ptr<int> p =make_shared<int>(30);//效率比下面的高 
shared_ptr<int> q (new int (42));// 不推荐,为了避免智能指针与普通指针的混用,所以最后使用make_shared,这样在内存分配之后立刻与智能指针绑定到一起.

shared_ptr的拷贝和赋值

auto p = make_shared<int>(42);
auto q(p);
auto r = p; //p递增,r递减。

每个shared_ptr都有一个关联的计数器,通常称为引用计数器

无论何时我们拷贝一个shared_ptr,引用计数就加一,当我们给shared_ptr重新赋值或者销毁的时候,引用计数就减一,直到引用计数为0,shared_ptr就会自动释放自己管理的对象。

shared_ptr自动销毁所管理的对象

 shared_ptr的析构函数会递减它所指向的对象的引用计数,当引用计数变为0时,shared_ptr就会通过析构函数自动释放自己所管理的对象。当动态对象不再使用时,shared_ptr会自动释放对象,这一特性使得动态内存的使用变得非常容易(尽量使用智能指针管理动态内存)。如果将shared_ptr放于容器中,而后不再需要全部元素,而只是使用其中一部分,要记得用erase删除不再需要的那些元素。

  在多线程程序中,一个对象如果被多个线程访问,一般使用shared_ptr,通过引用计数来保证对象不被错误的释放导致其他线程访问出现问题。

使用动态内存的原因:

  • 程序不知道自己需要多少对象。

  • 程序不知道所需对象的准确类型。

  • 允许多个对象共享相同的状态

shared_ptr存在问题

循环引用

class B;
class A
{
public:
  shared_ptr<B> m_b;
};

class B
{
public:
  shared_ptr<A> m_a;
};

void fun()
{
	shared_ptr<A> pa(new A); // new出来的A的引用计数此时为1
	shared_ptr<B> pb(new B); // new出来的B的引用计数此时为1
	pa->m_b = b; // B的引用计数增加为2
	pb->m_a = a; // A的引用计数增加为2
}

int main()
{
	fun();
	return 0;
}

  

    分析class A对象的引用情况,该对象被main函数中的pa和class B对象中的ptr管理,因此pa引用计数是2,class B对象同理。

   在这种情况下,在fun函数结束的时候,pa和pb的析构函数被调用,但是class A对象和class B对象仍然被一个智能指针管理,pa和pb引用计数变成1,于是这两个对象的内存无法被释放,造成内存泄漏,如下图所示:

因此,在这里标准库就引用了weak_ptr,将类里面的shared_ptr换成weak_ptr即可,由于weak_ptr并不会增加引用计数use的值,所以这里就能够打破shared_ptr所造成的循环引用问题。但是这里要注意一点,就是weak_ptr并不能单独用来管理空间。

weak_ptr

由于在shared_ptr单独使用的时候会出现循环引用的问题,造成内存泄漏,所以标准库又从boost库当中引入了weak_ptr(弱引用:不更改引用计数,类似普通指针)。对上面的测试用例进行修改:

class B;
class A
{
public:
  weak_ptr<B> m_b;
};

class B
{
public:
  weak_ptr<A> m_a;
};

void fun()
{
	shared_ptr<A> pa(new A); // new出来的A的引用计数此时为1
	shared_ptr<B> pb(new B); // new出来的B的引用计数此时为1
	pa->m_b = b; // B的引用计数增加为2
	pb->m_a = a; // A的引用计数增加为2
}

int main()
{
	fun();
	return 0;
}

解决方法很简单,把class A或者class B中的shared_ptr改成weak_ptr即可,由于weak_ptr不会增加shared_ptr的引用计数,所以pa和pb中有一个的引用计数为1,在pa和pb析构时,会正确地释放掉内存。

unique_ptr

unique_ptr 独占智能指针,某个时刻只能有一个unique_ptr 指向一个给定对象。当unique_ptr 被销毁时,它所指向的对象也被销毁。unique_ptr 不支持拷贝赋值等操作,除非这个unique_ptr将要被销毁,这种情况,编译器执行一种特殊的"拷贝"

unique_ptr<int> p1(new int(42));//必须直接初始化。
unique_ptr<int> p2(p1);//error
unique_ptr<int> p3 = p1;/error

unique_ptr<int> clone(int p)
{
  unique_ptr<int> ret(new int(p));
  return ret; //ok
}

 虽然不能拷贝或者赋值unique_ptr,但是通过调用release或者reset将指针的所有权从一个(非const)unique_ptr转移给另一个unique_ptr。调用release会切断unique_ptr和它原来管理对象间的联系。release返回的指针通常用来初始化另一个智能指针或者给另一个智能指针赋值。

如何选择智能指针?

  如果程序要使用多个指向同一个对象的指针,应选择shared_ptr。这样的情况包括:有一个指针数组,并使用一些辅助指针来标示特定的元素,如最大的元素和最小的元素;两个对象包含都指向第三个对象的指针;STL容器包含指针。很多STL算法都支持复制和赋值操作,这些操作可用于shared_ptr,但不能用于unique_ptr(编译器发出warning)和auto_ptr(行为不确定)。如果你的编译器没有提供shared_ptr,可使用Boost库提供的shared_ptr。

  如果程序不需要多个指向同一个对象的指针,则可使用unique_ptr。如果函数使用new分配内存,并返还指向该内存的指针,将其返回类型声明为unique_ptr是不错的选择。这样,所有权转让给接受返回值的unique_ptr,而该智能指针将负责调用delete。可将unique_ptr存储到STL容器在那个,只要不调用将一个unique_ptr复制或赋给另一个算法(如sort())。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值