Auto_ptr和shared_ptr

C++内存区划分相关的基础知识:(参考:《Exceptional C++》)

Const Data: The const data area stores string literals and other data whose values are known at compile-time. No objects of class type can exist in this area。这部分数据在定义之后是read-only。另外在很多特定的编译器中,对于这部分数据采用了常量折叠的编译优化方式。

Stack: 易懂

Free Store: new/delete方式申请的动态空间

Heap: malloc/free方式申请的动态空间

Global/Static: 易懂

补充:

常量折叠:const int i = 10;//编译器优化之后放在符号表中,同时在编译阶段对变量进行值替换(但是不同于宏定义),该变量在取地址之后会分配空间(如果没有对const常量进行取地址操作符或者extern来声明和定义的话,那么是不会为const常量分配内存空间的),栈空间或者全局空间。(参考:http://blog.csdn.net/yby4769250/article/details/7359278)

Free Store和Heap的区别:默认的new/delete是不同的编译器在malloc/free的基础上实现的,heap和自由存储区不相同,同时在heap上memory allocated in one area cannot be safely deallocated in the other。因此别试图用calloc(), malloc(), realloc申请的内存用::operator delete的释放。优先使用new/delete

Auto_ptr和shared_ptr正文

auto_ptr和shared_ptr都是智能指针的一种实现,所谓智能指针,多数情况下都是指这样的一些对象:

1. 内部有一个动态分配对象的指针,拥有该对象的使用权和所有权(独占或共享)。

2. 重载*和-<操作,行为上跟所拥有的对象的指针一致。

3. 当自身的生命期结束的时候,会做一些跟拥有对象相关的清理动作。

一.auto_ptr

auto_ptr是现在标准库里面一个轻量级的智能指针的实现,存在于头文件 memory中,之所以说它是轻量级,是因为它只有一个成员变量(拥有对象的指针),相关的调用开也非常小。下面的代码来自于VC++里面的源码:里面有个auto_ptr_ref的数据结构,我们可以把它忽略,这个只是内部使用的代理结构,用于一些隐式的const变化,我们客户端代码通常不会直接使用到它。我们可以看到除了构造函数,拷贝构造函数,赋值函数,析构函数和两个重载操作符(*,-<)外,还有get,release和reset三个函数,它们的作用是:1. get,获得内部对象的指针2. release,放弃内部对象的所有权,将内部指针置为空3. reset,销毁内部对象并接受新的对象的所有权(如果使用缺省参数的话,也就是没有任何对象的所有权)下面的例程来自Exceptional C++,Item 37:

// Example 2: Using an auto_ptr
//
void g()
{
T* pt1 = new T;
// right now, we own the allocated object
// pass ownership to an auto_ptr
auto_ptr pt2( pt1 );
// use the auto_ptr the same way
// we'd use a simple pointer
*pt2 = 12; // same as "*pt1 = 12;"

pt2-<SomeFunc(); // same as "pt1-<SomeFunc();"
// use get() to see the pointer value
assert( pt1 == pt2.get() );
// use release() to take back ownership
T* pt3 = pt2.release();
// delete the object ourselves, since now
// no auto_ptr owns it any more
delete pt3;

}// pt2 doesn't own any pointer, and so won't
// try to delete it... OK, no double delete


// Example 3: Using reset()
//
void h()
{
auto_ptr pt( new T(1) );
pt.reset( new T(2) );
// deletes the first T that was
// allocated with "new T(1)"

}// finally, pt goes out of scope and
// the second T is also deleted

从上面的例子来看,auto_ptr的使用很简单,通过构造函数拥有一个动态分配对象的所有权,然后就可以被当作对象指针来使用,当auto_ptr对象被销毁的时候,它也会自动销毁自己拥有所有权的对象(嗯,标准的RAAI做法),release可以用来手动放弃所有权,reset可用于手动销毁内部对象。但实际上,auto_ptr是一个相当容易被误用并且在实际中常常被误用的类。原因是由于它的对象所有权占用的特性和它非平凡的拷贝行为。

auto_ptr的对象所有权是独占性的!

这决定了不可能有两个auto_ptr对象同时拥有同一动态对象的所有权,从而也导致了auto_ptr的拷贝行为是非对等的,其中伴随着对象所有权的转移。我们仔细观察auto_ptr的源码就会发现拷贝构造和赋值操作符所接受的参数类型都是非const的引用类型(auto_ptr< Ty>& ),而不是我们一般应该使用的const引用类型,查看源码我们会发现:

  auto_ptr(auto_ptr< Ty>& _Right) _THROW0()
                 : _Myptr(_Right.release())
{ // construct by assuming pointer from _Right auto_ptr
}
template< span style='font-size:13px;font-style:normal;font-weight:normal;color:rgb(51, 51, 51);' >class</span> _Other<
                 auto_ptr< Ty>& operator=(auto_ptr< Other>& _Right) _THROW0()
{// assign compatible _Right (assume pointer)
  reset(_Right.release());
  return (*this);
}
拷贝过程中被拷贝的对象(_Right)都会被调用release来放弃所包括的动态对象的所有权,动态对象的所有权被转移了,新的auto_ptr独占了动态对象的所有权。也就是说被拷贝对象在拷贝过程中会被修改,拷贝物与被拷贝物之间是非等价的。这意味着如下的代码是错误的(例程来自 Exceptional C++ Item 37):

// Example 6: Never try to do work through

//            a non-owning auto_ptr
//
void f()
{
     auto_ptr pt1( new T );
     auto_ptr pt2;

     pt2 = pt1; // now pt2 owns the pointer, and

// pt1 does not
     pt1-<DoSomething();

// error: following a null pointer
}
同时也不要将auto_ptr放进标准库的容器中,否则在标准库容器无准备的拷贝行为中(标准库容器需要的拷贝行为是等价的),会导致难以发觉的错误。(请参考Exceptional C++ Item 37获得更多信息)auto_ptr特殊的拷贝行为使得使用它来远距离传递动态对象变成了一件十分危险的行为,在传递的过程中,一不小心就会留下一些实际为空但程序本身却缺少这样认知 auto_ptr对象。

使用const auto_ptr的方式来保证auto_ptr对象不失去自助权:

const auto_ptr<T> pt1( new T );
    // making pt1 const guarantees that pt1 can
    // never be copied to another auto_ptr, and
    // so is guaranteed to never lose ownership
auto_ptr<T> pt2( pt1 ); // illegal
auto_ptr<T> pt3;
pt3 = pt1; // illegal
pt1.release(); // illegal
pt1.reset( new T ); // illegal

简单的说来,auto_ptr适合用来管理生命周期比较短或者不会被远距离传递的动态对象,使用auto_ptr来管理动态分配对象需要注意四点:

1) 不要使用auto_ptr指向静态分配对象的指针,因为auto_ptr撤销的时候会释放静态指针,导致未定义行为。

2) 不要使用两个auto_ptr对象指向同一对象,形式一:使用同一指针来初始化或者reset两个不同的auto_ptr对象。形式二:使用一个auto_ptr对象的get函数来初始化另外一个

auto_ptr.

3)不要使用auto_ptr对象保存指向动态分配数组的指针,auto_ptr释放的时候内部采用的是delete并非delete[].

4)不要将auto_ptr存储到容器中,因为auto_ptr拷贝是破坏性的,会破坏被拷贝者。

二. shared_ptr

shared_ptr是Boost库所提供的一个智能指针的实现,正如其名字所蕴意的一样:

An important goal of shared_ptr is to provide a standard shared-ownership pointer.

shared_ptr的一个重要目的就是为了提供一个标准的共享所有权的智能指针。

—— Boost库文档

没错,shared_ptr就是为了解决auto_ptr在对象所有权上的局限性(auto_ptr是独占的),在使用引用计数的机制上提供了可以共享所有权的智能指针,当然这不会没有任何额外的代价……

首先一个shared_ptr对象除了包括一个所拥有对象的指针(px)外,还必须包括一个引用计数代理对象(shared_count)的指针(pn)。而这个引用计数代理对象包括一个真正的多态的引用计数对象(sp_counted_base)的指针(_pi),真正的引用计数对象在使用VC编译器的情况下包括一个虚表,一个虚表指针,和两个计数器。下图中result是一个shared_ptr对象,我们可以清楚看到它展开后所包含的数据:

假设我们有多个(5个以上)shared_ptr共享一个动态对象,那么每个shared_ptr的开销比起只使用原生指针的开销大概在3,4倍左右(这还是理想状况,忽略了动态分配带来的俑余开销)。如果只有一个shared_ptr独占动态对象,空间上开销更是高度十数倍!而auto_ptr的开销只是使用原生指针的两倍。时间上的开销主要在初始化和拷贝操作上,*和-<操作符重载的开销跟auto_ptr是一样的。当然开销并不是我们不使用shared_ptr的理由,永远不要进行不成熟的优化,直到性能分析器告诉你这一点,这是Hurb提出的明智的建议。以上的说明只是为了让你了解强大的功能背后总是伴随着更多的开销,shared_ptr应该被使用,但是也不要过于滥用,特别是在一些auto_ptr更擅长的地方。

下面是shared_ptr的类型定义:

template<class T> class shared_ptr {
    public:
      typedef T element_type; 

      shared_ptr(); // never throws

      template<class Y> explicit shared_ptr(Y * p);

      template<class Y, class D> shared_ptr(Y * p, D d);

      ~shared_ptr(); // never throws 

      shared_ptr(shared_ptr const & r); // never throws

      template<class Y> shared_ptr(shared_ptr<Y> const & r); // never throws

      template<class Y> explicit shared_ptr(weak_ptr<Y> const & r);

      template<class Y> explicit shared_ptr(std::auto_ptr<Y> & r); 

      shared_ptr & operator=(shared_ptr const & r); // never throws 

      template<class Y> shared_ptr & operator=(shared_ptr<Y> const & r); // never throws

      template<class Y> shared_ptr & operator=(std::auto_ptr<Y> & r); 

      void reset(); // never throws

      template<class Y> void reset(Y * p);

      template<class Y, class D> void reset(Y * p, D d);
 

      T & operator*() const; // never throws

      T * operator->() const; // never throws

      T * get() const; // never throws
 

      bool unique() const; // never throws

      long use_count() const; // never throws

      operator unspecified-bool-type() const; // never throws
 

      void swap(shared_ptr & b); // never throws
 }; 
大多数成员函数都跟auto_ptr类似,但是没有了release(请看注释),reset用来放弃所拥有对象的所有权或拥有对象的变更,会引起原有对象的引用计数的减少。
Note:
Boost文档里面的QA说明了为什么不提供release函数
Q. Why doesn't shared_ptr provide a release() function?

A. shared_ptr cannot give away ownership unless it's unique() because the other copy will still destroy the object.

Consider:

shared_ptr<int> a(new int);

shared_ptr<int> b(a); // a.use_count() == b.use_count() == 2

int * p = a.release();

// Who owns p now? b will still call delete on it in its destructor.

Furthermore, the pointer returned by release() would be difficult to deallocate reliably,

as the source shared_ptr could have been created with a custom deleter.

use_count返回引用计数的个数,unique拥于确认是否是独占所有权(use_count为1),swap用于交换两个shared_ptr对象(即交换所拥有的对象),有一个bool类型转换操作符使得shared_ptr可用于需要的bool类型的语境下,比如我们通常用if(pointer)来判断某个指针是否为空。Boost库里面有很多shared_ptr的使用例程,文档里面也列举了许许多多shared_ptr的用途,其中最有用也最常用的莫过于传递动态分配对象,有了引用计数机制,我们现在可以安全地将动态分配的对象包裹在shared_ptr里面跨越模块,跨越线程的边界传递。shared_ptr为我们自动管理对象的生命周期,嗯,C++也可以体会到Java里面使用引用的美妙之处了。

另外,还记得Effective C++里面(或者其它的C++书籍),Scott Meyer告诉你的:在一个由多个模块组成的系统里面,一个模块不用试图自己去释放另外一个模块分配的资源,而应该遵循谁分配谁释放的原则。正确的原则但是有时难免有时让人忽略(过于繁琐),将资源包装在shared_ptr里面传递,而shared_ptr保证了在资源不再被拥有的时候,产生资源的模块的delete语句会被调用。shared_ptr是可以拷贝和赋值的,拷贝行为也是等价的,并且可以被比较,这意味这它可被放入标准库的一般容器(vector,list)和关联容器中(map)。shared_ptr可以用来容纳多态对象,比如所下面的例子:

class Base

{
}

class Derived : public Base
{
}
shared_ptr sp_base(new Derived);

甚至shared_ptr也具备多态的行为:

Derived* pd = new Derived;
shared_ptr sp_derived(pd);

shared_ptr sp_base2(sp_derived);

上面的语句是合法的,shared_ptr会完成所需的类型转换,当shared_ptr的模版参数Base的确是Derived的基类的时候。

最后是一个小小的提醒,无论是使用auto_ptr还是shared_ptr,都永远不要写这样的代码:

A* pa = new A;

xxx_ptr ptr_a_1(pa);

xxx_ptr ptr_a_2(pa);

很明显,在ptr_a_1和ptr_a_2生命周期结束的时候都会去删除pa,pa被删除了两次,这肯定会引起你程序的崩溃,当然,这个误用的例子比较明显,但是在某种情况下,可能会一不小心就写出如下的代码(嗯,我承认我的确做过这样的事情):

void DoSomething(xxx_ptr)
{
//do something
}

class A
{
         doSomething()
         {
                 xxx_ptr ptr_a(this);
                 DoSomething(ptr_a);
         }
};

int main()
{
         A a;
         a.doSomething();
//continue do something with a, but it was already destory
}
在函数a.doSomething()里面发生了什么事情,它为了调用DoSomething所以不得不把自己包装成一个xxx_ptr,但是忘记在函数结束的时候,xxx ptr_a被销毁的同时也销毁了自己,程序或者立刻崩溃或者在下面的某个时间点上崩溃!所以你在使用智能指针做为函数参数的时候请小心这样的误用,有时候使用智能指针作为函数参数不一定是一个好注意。比如请遵循下面的建议,请不要在属于类型A的接口的一部分的非成员函数或者跟A有紧密联系的辅助函数里面使用xxx_ptr作为函数的参数类型。
参考: http://www.cnblogs.com/xiaouisme/archive/2012/07/30/2615365.html
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值