八 智能指针类

来源:互联网 发布:淘宝评价图片怎么删除 编辑:程序博客网 时间:2024/05/17 02:32
本文介绍几个常用的智能指针类。
auto_ptr
    98 C++标准只规定了一个智能指针,就是
template <class Type>
class auto_ptr
    下面是示例代码:
#include <memory>
using namespace std;


int _tmain(int argc, _TCHAR* argv[])
{
    auto_ptr<string> spString(new string("hello,world"));
    size_t length=spString->length();
    return 0;
}
    模板参数Type是auto_ptr管理的类型,auto_ptr<string>的构造函数接受类型为string*的指针,析构函数将用delete释放该指针。
    auto_ptr模板类提供了get函数用来获得裸指针T*,也可以通过reset来重新接受一个新的指针,同时释放内部的指针。
    auto_ptr模板类提供了operator ->用来模仿指针的调用函数行为,提供了operator*用来模仿指针的间接引用功能。
    如果想手动回收指针,可以调用release函数,该函数将返回内部管理的裸指针并且
内部成员置为0。
    auto_ptr经过专门的设计,使它拥有一个奇特的功能,所有权转移,也是很多人的烦恼之源。下面是所有权转移导致的几种烦恼:
    1)auto_ptr不能用于STL容器,这是因为当我们使用像sort这样的函数对容器排序时,该函数内部会创建auto_ptr的副本,所有权转移到副本上,而容器中的auto_ptr元素已经不再管理原来的指针,所以这是错误的用法。大部分的auto_ptr的实现中将拷贝构造函数和拷贝赋值函数的参数故意定为非常量引用,而大多数容器插入函数都接受常量引用,所以,像下面的代码,通常是不能编译通过的。
    vector<auto_ptr<char> > v;
    auto_ptr<char> spBuffer(new char[100]);
    v.push_back(spBuffer);
    2)如果你使用auto_ptr来管理你的成员指针,小心的处理对象拷贝机制

class A
{
public:
    A(string const& str):_spStr(new string(str))
    {
    }

private:
    auto_ptr<string> _spStr;
};


int _tmain(int argc, _TCHAR* argv[])
{
    string str("hello,world");
    A a1(str);
    A a2(a1);
    return 0;
}
    当A a2(a1)执行后,a2._spStr将拥有指针所有权,而a1._spStr将释放指针所有权,这可能是你不想要的情况。你可以将拷贝构造函数和拷贝赋值函数禁止,或者提供自己的深拷贝版本,或者使用const auto_ptr作为成员变量,因为const auto_ptr不可被拷贝,也就不可能失去所有权。

class A
{
public:
    A(){}
    A(string const& str):_spStr(new string(str))
    {
    }
    A(A const& rhs):_spStr(new string(*rhs._spStr))
    {
        
    }
    A& operator = (A const& rhs)
    {
        if(this!=&rhs)
        {
            _spStr.reset(new string(*rhs._spStr));
        }
        return *this;
    }
private:
    auto_ptr<string> _spStr;
};


int _tmain(int argc, _TCHAR* argv[])
{
    string str("hello,world");
    A a1(str);
    A a2;
    a2=a1;
    return 0;
}

使用auto_ptr const 成员
class A
{
public:
    A(){}
    A(string const& str):_spStr(new string(str))
    {
    }

private:
    auto_ptr<string> const _spStr;
};


int _tmain(int argc, _TCHAR* argv[])
{
    string str("hello,world");
    A a1(str);
    A a2(a1);
    return 0;
}
error C2558: class 'std::auto_ptr<_Ty>' : no copy constructor available or copy constructor is declared 'explicit'

    另外,由于auto_ptr在析构函数中使用delete语句而不是delete[],所以auto_ptr不能用于包装指向数组的指针,除非包装的是预定义类型。因为
char* p=new char[100];
...
delete p;
是可以的,所以你可以使用:
auto_ptr<char> sp(new char[100]());
...
    所有权转移也会给我们带来一些好东西。比如:
string* F1()
{
    return new string("hello,world");
}

auto_ptr<string> F2()
{
    return auto_ptr<string>(new string("hello,world"));
}

int _tmain(int argc, _TCHAR* argv[])
{
    string* p=F1();
    auto_ptr<string> sp=F2();
    return 0;
}

    如果忽略了这两个函数的返回值,F1返回的指针将再也找不到,内存泄漏,F2会返回临时变量,并且会被安全的释放掉,没有内存泄漏。并且当F2函数较为复杂时,返回auto_ptr提升了异常安全性。异常安全性将在后面介绍。
shared_ptr

1) shared_ptr 的结构,参考我的另一篇文章:

http://blog.csdn.net/sheismylife/article/details/8551369

2)引用计数在多线程环境下的性能问题

    引用计数变量是一个整数,在单线程环境下是简单的加1和减1,在多线程环境中,如果shared_ptr被用来复制出新的shared_ptr,而有的shared_ptr又在销毁。引用计数器的值的修改是需要线程安全的。如果有一个shared_ptr对象始终都在一个线程里面使用,尽管衍生出来它的很多副本,但都没有离开这个线程,我们是不应该使用同步技术保护引用计数的修改操作的。比较常用的做法是通过模板参数来控制。但是boost的作者选择了较简单但并不完美的方案,如果我们的程序中有多线程,就通过宏告诉shared_ptr,它的内部就会不管实际情况如何,都用锁。

    BOOST_SP_DISABLE_THREADS告诉shared_ptr这只是单线程,不用考虑同步;其他相关宏定义在sp_counted_base.hpp文件中。
    即便是锁也是有讲究的。请看下面这段描述:
It should be pointed out that both IRIX and Linux support optimized atomic operations that are much faster than the following code sequence:
pthread_mutex_lock ( &count_mutex );
   count++;
pthread_mutex_unlock ( &count_mutex );
On IRIX __fetch_and_add while under Linux __sync_fetch_and_add (gcc) or _InterlockedIncrement (Intel compiler) would be much faster.
    所以刚才介绍的宏还有帮助我们选择使用哪种同步机制的功能.Boost的配置环境做得比较智能(默认是多线程)。经过测试,在windows 、VC、AMD 2800+cpu环境下,更新引用计数器的方法是调用_InterlockedIncrement方法,在linux、InterX86 cpu、GNU C++环境下采用内联汇编方法,因此性能比使用mutex的方法好很多。
    下面引自csdn博客上一位程序员的测试结论:boost 智能指针 --- 关于性能的少数派报告
    比起 boost 1.33 ,这个时候的 boost 1.32 shared_ptr 平均运行时间多出了约 112% ,weak_ptr 的平均运行时间多出了约 126.2% !我们终于看到了新的实现在性能上的好处
    注意:boost1.32使用的是Mutex锁,1.33以后才改进为我前面描述的原子操作。我已经将shared_ptr从boost库中分离出来,毕竟boost太大了。我已经在Linux和windows上测试过了,大家感兴趣的可以使用。在以下路径下//192.168.22.26/ShareD/Training/C++TrainingHistory/2007-03/CC1/new
有一个tr1子目录(代表C++标准技术报告1)。
使用示例:
#include "tr1/shared_ptr.hpp"
#include <iostream>
#include <string>
using namespace boost;
using namespace std;

void f()
{
    shared_ptr<string> spStr1(new string("hello,world"));
    cout<<spStr1.use_count()<<endl;
    shared_ptr<string> spStr(spStr1);
    cout<<spStr1.use_count()<<endl;
}
int main(void)
{
    f();
    return 0;
}
    我已经在下列平台测试过shared_ptr:
操作系统
编译器
CPU
测试结果
Windows XP SP2
VC8
AMD 2800+
通过
Linux SuSe Enterprise Server 9
gcc3.3.3
x86
通过
Solaris
gcc 3.4.2
sparc v9
g++ -mcpu=v9 通过
Solaris
CC
sparc v9
通过

    shared_ptr是如何实现整数的原子操作呢?在sp_counted_base.hpp文件中,提供了一大堆宏判断:(这些宏通常不需要设置)
#include "../config.hpp"
#if defined( BOOST_SP_DISABLE_THREADS )//单线程
# include "sp_counted_base_nt.hpp"
#elif defined( BOOST_SP_USE_PTHREADS )//使用PThread线程模型,mutex
# include "sp_counted_base_pt.hpp"
#elif defined( __GNUC__ ) && ( defined( __i386__ ) || defined( __x86_64__ ) )
# include "sp_counted_base_gcc_x86.hpp"//gcc + x86
#elif defined( __GNUC__ ) && defined( __ia64__ ) && !defined( __INTEL_COMPILER )
# include "sp_counted_base_gcc_ia64.hpp"//gcc + ia64
#elif defined( __MWERKS__ ) && defined( __POWERPC__ )
# include "sp_counted_base_cw_ppc.hpp"
#elif defined( __GNUC__ ) && ( defined( __powerpc__ ) || defined( __ppc__ ) )
# include "sp_counted_base_gcc_ppc.hpp"
#elif defined(__GNUC__) && ( defined( __sparcv8 ) || defined( __sparcv9 ) )
# include "sp_counted_base_gcc_sparc.hpp"//gcc + sparc
#elif defined( WIN32 ) || defined( _WIN32 ) || defined( __WIN32__ )
# include "sp_counted_base_w32.hpp"
#elif !defined( BOOST_HAS_THREADS )
# include "sp_counted_base_nt.hpp"
#elif defined( BOOST_HAS_PTHREADS )
# include "sp_counted_base_pt.hpp"
#else
// Use #define BOOST_DISABLE_THREADS to avoid the error
# error Unrecognized threading platform
#endif

    根据不同的情况,包含不同的文件,每个文件内部都实现了sp_counted_base类,但是类的实现细节和平台相关,多数使用了特定cpu的内联汇编代码:
如sp_counted_base_gcc_x86.hpp文件中
namespace detail
{

inline int atomic_exchange_and_add( int * pw, int dv )
{
    // int r = *pw;
    // *pw += dv;
    // return r;

    int r;

    __asm__ __volatile__
    (
        "lock/n/t"
        "xadd %1, %0":
        "=m"( *pw ), "=r"( r ): // outputs (%0, %1)
        "m"( *pw ), "1"( dv ): // inputs (%2, %3 == %1)
        "memory", "cc" // clobbers
    );

    return r;
}

inline void atomic_increment( int * pw )
{
    //atomic_exchange_and_add( pw, 1 );

    __asm__
    (
        "lock/n/t"
        "incl %0":
        "=m"( *pw ): // output (%0)
        "m"( *pw ): // input (%1)
        "cc" // clobbers
    );
}

inline int atomic_conditional_increment( int * pw )
{
    // int rv = *pw;
    // if( rv != 0 ) ++*pw;
    // return rv;

    int rv, tmp;

    __asm__
    (
        "movl %0, %%eax/n/t"
        "0:/n/t"
        "test %%eax, %%eax/n/t"
        "je 1f/n/t"
        "movl %%eax, %2/n/t"
        "incl %2/n/t"
        "lock/n/t"
        "cmpxchgl %2, %0/n/t"
        "jne 0b/n/t"
        "1:":
        "=m"( *pw ), "=&a"( rv ), "=&r"( tmp ): // outputs (%0, %1, %2)
        "m"( *pw ): // input (%3)
        "cc" // clobbers
    );

    return rv;
}
    boost一共提供了10个这样的文件,除了一个不使用外,也就是有九种实现,如果超过了这九种,编译器将报错表示不支持。
    注意:Fedora7已经安装了boost库。头文件路径在/usr/include/boost。