智能指针shared_ptr【无锁设计基于GCC】

1. shared_ptr 介绍

  使用过Boost的话对shared_ptr一定有很深的印象。多个shared_ptr指向同一个对象,每个shared_ptr会使对象的引用计数加+1,当引用计数为0时,

对象将被析构。本文实现一个简洁版本的shared_ptr,并没有太多跨平台特性,实现代码可以再GCC上运行。

      本文中的引用计数由ref_count_t类实现,参见下文的详细分析。

  详文另见:

  代码详见:http://ffown.sinaapp.com/?p=49

  svn co http://ffown.googlecode.com/svn/trunk/fflib/lib

2.  shared_ptr 的构造

     我们期望shared_ptr的行为尽量的接近原始指针的行为。所以shared_ptr应该支持三种构造方式

  a. 空构造类似与void* p =NULL;

      b. shared_ptr可以通过原始对象指针构造,类似于void* p = q;

  c. shared_ptr 可以通过已存在的shared_ptr构造。

  首先shared_ptr是一个模板类,其由连个属性。

private:
    object_t*       m_dest_ptr;
    ref_count_t*    m_ref_count;
};

其中m_dest_ptr 指向目标对象, m_ref_count 用来记录该对象的引用计数。为了简单,shared_ptr类遵循一个原则m_dest_ptr和m_ref_count  同时为NULL,或同时不为NULL。

其中 object_t 为模板类型的别名。

template<typename T>
class shared_ptr_t
{
public:
    typedef T               object_t;
    typedef shared_ptr_t<T> self_type_t;

  1> 空构造目标对象和引用计数默认都为空。

template<typename T>
shared_ptr_t<T>::shared_ptr_t(object_t* p):
    m_dest_ptr(p),
    m_ref_count(NULL)
{
    if (NULL != m_dest_ptr)
    {
        m_ref_count = new ref_count_t();
    }
}

  share_ptr_t<int> p;

  2> 支持原始对象指针作为构造函数参数

template<typename T>
shared_ptr_t<T>::shared_ptr_t(object_t* p):
    m_dest_ptr(p),
    m_ref_count(NULL)
{
    if (NULL != m_dest_ptr)
    {
        m_ref_count = new ref_count_t();
    }
}

  用例:share_ptr_t<int> p(new int());   3> 使用已存在的shared_ptr 构造

template<typename T>
shared_ptr_t<T>::shared_ptr_t(self_type_t& p):
    m_dest_ptr(p.get()),
    m_ref_count(p.ger_ref_count())
{
    if (NULL != m_dest_ptr)
    {
        m_ref_count->inc();
    }
}

  用例: share_ptr_t<int> q(p);

3. shared_ptr 获取引用计数或原始指针

  有时需要知道shared_ptr当前引用计数的值,通过shared_ptr获取原始指针理所当然。So:

size_t       ref_count() const       { return m_ref_count != NULL? (size_t)m_ref_count->size(): 0; }

所以很容易验证shared_ptr的行为:

shared_ptr_t p(new int());
assert(p.ref_count() == 1);
shared_ptr_t<int> q(p);
assert(q.ref_count() == 1);

4. 减少引用计数

  shared_ptr需要显示的析构对象,所以提供reset接口,当目标对象已经创建并且引用计数达到零时(即不再有shared_ptr保存目标对象的控制权),析构目标对象。

template<typename T>
void shared_ptr_t<T>::reset()
{
    if (m_dest_ptr)
    {
        if (true == m_ref_count->dec_and_check_zero())
        {
            delete m_ref_count;
            delete m_dest_ptr;
        }
        m_ref_count = NULL;
        m_dest_ptr = NULL;
    }
}

5. shared_ptr 的析构

  很简单,减少引用计数。

template<typename T>
shared_ptr_t<T>::~shared_ptr_t()
{
    reset();
}

6. 向原始指针一样使用shared_ptr

  可以这样使用shared_ptr

  struct foo_t { int a; }

  shared_ptr_t<foo_t> p(new foo_t());

  (*p).a = 100;

  p->a = 100;

  if(p) cout << "p not null!\n";

     所以提供如下接口:

template<typename T>
typename shared_ptr_t<T>::object_t&    shared_ptr_t<T>::operator*()
{
    assert(NULL != m_dest_ptr);
    return *m_dest_ptr;
}

template<typename T>
typename shared_ptr_t<T>::object_t*    shared_ptr_t<T>::operator->()
{
    assert(NULL != m_dest_ptr);
    return m_dest_ptr;
}

7. Lock Free引用计数实现

  GCC中已经定义了一些atomic operation,但是查阅资料后,应该是对Intel的平台支持较好,其他平台支持不确定。故把atomic操作封装成宏。

#define ATOMIC_ADD(src_ptr, v)                         (void)__sync_add_and_fetch(src_ptr, v)
#define ATOMIC_SUB_AND_FETCH(src_ptr, v)  __sync_sub_and_fetch(src_ptr, v)

  ref_count_t 实现很简单:

class ref_count_t
{
    typedef  volatile long atomic_t;
public:
    ref_count_t():
        m_ref_num(1)
    {}
    ~ref_count_t()
    {}

    inline void inc()
    {
        ATOMIC_ADD(&m_ref_num, 1);
    }
    inline bool dec_and_check_zero()
    {
        return 0 == ATOMIC_SUB_AND_FETCH(&m_ref_num, 1);
    }
    inline atomic_t size()
    {
        return m_ref_num;
    }

private:
    atomic_t m_ref_num;
};
#endif

8. 线程安全性

1. 单线程多个shared_ptr指向不同的对象,安全。

2. 单线程多个shared_ptr指向相同的对象,安全。

3. 多线程多个操作不同的shared_ptr, 指向不同的对象,安全。

4. 多线程多个操作不同的shared_ptr, 指向相同对象,shared_ptr安全(也就是引用计数维护正确),对于原始对象操作依赖于用户。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏编程坑太多

python中yield\\send好处

673
来自专栏超然的博客

ECMAScript 6 笔记(一)

       1996年11月,JavaScript的创造者Netscape公司,决定将JavaScript提交给国际标准化组织ECMA,希望这种语言能够成为国...

843
来自专栏数据结构与算法

P1182 数列分段Section II

题目描述 对于给定的一个长度为N的正整数数列A[i],现要将其分成M(M≤N)段,并要求每段连续,且每段和的最大值最小。 关于最大值最小: 例如一数列4 2 4...

2498
来自专栏desperate633

LintCode 二分查找题目分析代码

给定一个排序的整数数组(升序)和一个要查找的整数target,用O(logn)的时间查找到target第一次出现的下标(从0开始),如果target不存在于数组...

652
来自专栏Python小屋

详解Python变量作用域

本文暂时不讨论类定义中的变量(成员)作用域,改天可能会单独成文介绍。 变量作用域总起来说可以这么理解:1)在函数内如果只引用某个变量的值而没有为其赋新值,该变量...

4038
来自专栏IMWeb前端团队

bash 的字符串和数组

回顾下自己接触过的编程语言,字符串和数组真是基础中的基础。也因此,在接触一门新的语言的时候,非常有必要去熟悉该语言的字符串和数组。 字符串 声明和赋值 name...

1748
来自专栏Golang语言社区

map按key和按value排序

看一个题: 查找和排序 题目:输入任意(用户,成绩)序列,可以获得成绩从高到低或从低到高的排列,相同成绩 都按先录入排列在前的规则处理。 例示: jack 70...

2743
来自专栏xcywt

《Linux命令行与shell脚本编程大全》第二十二章 gawk进阶

gawk是一门功能丰富的编程语言,你可以通过它所提供的各种特性来编写好几程序处理数据。  22.1 使用变量 gawk编程语言支持两种不同类型的变量: 内建变量...

1846
来自专栏数据结构与算法

35:字符串的展开 5分,实在无能为力

35:字符串的展开 总时间限制: 1000ms 内存限制: 65536kB描述 在初赛普及组的“阅读程序写结果”的问题中,我们曾给出一个字符串展开的例子:如...

3407
来自专栏技术专栏

Scala入门与进阶(三)- 函数

默认参数:在函数定义时,允许指定参数的默认值 $SPARK_HOME/conf/spark-defaults.conf

673

扫描关注云+社区