C++11 标准库头文件模拟实现,无锁STL

系列文章目录



前言

暂不考虑支持多线程
常用STL的简单实现,主要内容百行左右, 避免冗余代码


● 智能指针模板

SharedPtr

在这里插入图片描述

#include <assert.h>
#include <atomic>


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

    explicit SharedPtr(T* ptr = nullptr) : m_ref(nullptr)
    {
        if (ptr)  m_ref = new reftype(ptr);
    }
    template <class Deleter>
    explicit SharedPtr(T* ptr, Deleter d) : m_ref(nullptr)
    {
        if (ptr) m_ref = new reftye_with_deleter<Deleter>(ptr, d);
    }
    SharedPtr(const SharedPtr& tocopy)
    {
        Acquire(tocopy.m_ref);
    }
    ~SharedPtr()
    {
        Release();
    }
    SharedPtr& operator=(const SharedPtr& tocopy)
    {
        if (this != &tocopy)
        {
            Release();
            Acquire(tocopy.m_ref);
        }
        return *this;
    }
    SharedPtr& operator=(T* ptr)
    {
        if (get() != ptr)
        {
            Release();
            if (ptr) m_ref = new reftype(ptr);
        }
        return *this;
    }
    T* get() const
    {
        return m_ref ? m_ref->m_ptr : nullptr;
    }
    void reset(T* ptr = nullptr)
    {
        Release();
        if (ptr) m_ref = new reftype(ptr);
    }
    template <class Deleter>
    void reset(T* ptr, Deleter d)
    {
        Release();
        if (ptr) m_ref = new reftye_with_deleter<Deleter>(ptr, d);
    }
    bool unique() const { return m_ref ? m_ref->m_count == 1 : true; }
    long use_count() const { return m_ref ? m_ref->m_count.load() : 0; }

    // test for pointer validity: defining conversion to unspecified_bool_type
    // and not more obvious bool to avoid implicit conversions to integer types
    typedef T*(SharedPtr<T>::*unspecified_bool_type)() const;
    operator unspecified_bool_type() const
    {
        if (m_ref && m_ref->m_ptr) return &SharedPtr<T>::get;
        else nullptr;
    }

    T& operator*() const
    {
        assert(m_ref && m_ref->m_ptr);
        return *( m_ref->m_ptr);
    }
    T* operator->() const
    {
        assert(m_ref && m_ref->m_ptr);
        return m_ref->m_ptr;
    }

private:
    struct reftype
    {
        reftype(T* ptr) : m_ptr(ptr), m_count(1) {}
        virtual ~reftype() {}
        virtual void delete_ptr() { delete m_ptr; }

        T*              m_ptr;
        std::atomic_int m_count;
    };
    template <class Deleter>
    struct reftye_with_deleter: public reftype
    {
        reftye_with_deleter(T* ptr, Deleter d) : reftype(ptr), m_deleter(d) {}
        virtual void delete_ptr() override
        {
            m_deleter(this->m_ptr);
        }
        Deleter m_deleter;
    };

    reftype* m_ref;

    void Acquire(reftype* ref)
    {
        m_ref = ref;
        if (ref) ref->m_count.fetch_add(1);
    }
    void Release()
    {
        if (m_ref)
        {
            if (! --m_ref->m_count )
            {
                m_ref->delete_ptr();
                delete m_ref;
            }
            m_ref = nullptr;
        }
    }
};

template <class T, class U>
bool operator==(const SharedPtr<T>& a, const SharedPtr<U>& b)
{
    return a.get() == b.get();
}
template <class T, class U>
bool operator!=(const SharedPtr<T>& a, const SharedPtr<U>& b)
{
    return a.get() != b.get();
}



//------------------------------------------------------------------------------
#include <iostream>
#include <stdio.h>
class TestSt
{
public:
    char buf[1024];
    char bux[4096];
public:
    TestSt() 
    { 
        printf("   const %p\n", this);
    };
    ~TestSt()
    {
        printf("~TestSt: %p\n", this);
    }
};

int main()
{
    std::atomic_int at;
    SharedPtr<TestSt> p_st(new TestSt());
    SharedPtr<TestSt> p_st2(p_st);

    SharedPtr<TestSt> p_st3(new TestSt());
    p_st3 = p_st2;
    std::cout << p_st.use_count() << " " << p_st2.use_count() << " " 
            << p_st3.use_count() << " " <<  std::endl;

}

● Vector

auto p = new T[N]; 申请空间,为每个元素p[i]调用T的构造函数
delete[] p; 先为每个元素调用析构函数,释放空间

1. 简单版本

在这里插入图片描述

new (m_values+i) T(val); 和 m_values[i] = val并不等价
当value_type包含虚函数时后者不能正确处理虚函数表指针,而前者却可以。
在这里插入图片描述
如图所示,new (m_values+i) T(val)可以正确处理虚表指针,而m_values[i] = val的_vptr.TestSt = 0x0

#include <stdlib.h>
#include <algorithm>

template <class T>
class Vector
{
private:
    T* m_values;
    size_t m_size, m_capacity;

public:
    typedef T value_type;
    typedef T* iterator;

    Vector(): m_values(nullptr), m_size(0), m_capacity(0) {}
    ~Vector()
    {
        for (size_t i = 0; i < m_size; i++)
        {
            m_values[i].~T();
        }
        free(m_values);
        m_values = nullptr;
        m_size = m_capacity = 0;
    }
    Vector(size_t n, const T& val): m_values(nullptr), m_size(n),m_capacity(n)
    {
        m_values =  (T*)malloc(sizeof(T) * m_capacity);
        for (size_t i = 0; i < n; i++)
        {
            new (m_values+i) T(val);
        }
        
    }
    Vector(const Vector<T>& tocopy)
    {
        m_values =  (T*)malloc(sizeof(T) * tocopy.m_capacity);
        m_size = tocopy.m_size;
        m_capacity = tocopy.m_capacity;
        for (size_t i = 0; i < m_size; i++)
        {
            new (m_values+i) T(tocopy.m_values[i]);
        }
    }
    Vector<T>& operator=(const Vector<T>& tocopy)
    {
        if (&tocopy == this) return *this;
        Vector<T> tmp(tocopy);
        std::swap(m_values, tmp.m_values);
        std::swap(m_size, tmp.m_size);
        std::swap(m_capacity, tmp.m_capacity);
        return *this;
    }
    value_type& operator[](size_t idx)
    {
        return m_values[idx];
    }
    void push_back(const T& val)
    {
        if (m_size + 1 > m_capacity)
        {
            m_capacity = m_capacity ? 2*m_capacity : 1;
            T* tmp = (T*)malloc(sizeof(T) * m_capacity);
            for (size_t i = 0; i < m_size; i++)
            {
                new (tmp+i) T(m_values[i]);
                m_values[i].~T();
            }
            free(m_values);
            m_values = tmp;
        }
        new (m_values+m_size) T(val);
        ++m_size;
    }
    void pop_back()
    {
        m_values[m_size].~T();
        // if ~T() _did_ throw, everything is OK,
        // but push_back Unable to handle throw
        --m_size;
    }
    void clear()
    {
        for (size_t i = 0; i < m_size; i++)
        {
            m_values[i].~T();
        }
        m_size = 0;
    }
    size_t size() { return m_size; }
    iterator begin()
    {
        return m_values;
    }
    iterator end()
    {
        return m_values + m_size;
    }
};

2. X

● 无锁栈

在这里插入图片描述

// lock_free_stack
template <class T, class Alloc=std::allocator<T>>
class lock_free_stack
{
private:
    struct Node
    {
        Node(const T& tocopy): m_data(tocopy), m_next(0) {}
        T m_data;
        Node* m_next = nullptr;
    };
    std::atomic<Node*> m_top;
    std::atomic_size_t m_size;
public:
    lock_free_stack(const lock_free_stack&) = delete;
    lock_free_stack& operator=(const lock_free_stack&) = delete;
    lock_free_stack(const lock_free_stack&&) = delete;
    lock_free_stack& operator=(const lock_free_stack&&) = delete;

    lock_free_stack()
    {
        m_top.store(nullptr);
        m_size.store(0);
    }
    T& top()
    {
        return m_top.load()->m_data	;
    }
    bool push(const T& tocopy)
    {
        Node* new_node = new (std::nothrow) Node(tocopy);
        if (!new_node) return false;
        new_node->m_next = m_top.load();
        while ( !m_top.compare_exchange_strong(new_node->m_next, new_node, std::memory_order_acq_rel) ) sched_yield();
        m_size.fetch_add(1);
        return true;
    }
    void pop()
    {
        // other threads spin
        Node* __always_top = nullptr;
        do
        {
            __always_top = m_top.load();
            if (!__always_top) return;
        } while ( !m_top.compare_exchange_strong(__always_top, __always_top->m_next, std::memory_order_acq_rel) );

		// ptr_ compare with cmp, if equal ptr_ = val ret true, not cmp = ptr_ ret false
        // ptr_.compare_exchange_strong(cmp, val, std::memory_order_acq_rel);
        
        __always_top->m_next = nullptr;
        delete __always_top;
        m_size.fetch_sub(1);
    }
    size_t size()
    {
        return m_size.load();
    }
    ~lock_free_stack() = default;
};

● 一种高性能队列实现

通过front ()和back() 获取大小为sizeof(T)的空间用于读写数据

在这里插入图片描述

//  yqueue is an efficient queue implementation. The main goal is
//  to minimise number of allocations/deallocations needed. Thus yqueue
//  allocates/deallocates elements in batches of N.
//
//  yqueue allows one thread to use push/back function and another one
//  to use pop/front functions. However, user must ensure that there's no
//  pop on the empty queue and that both threads don't access the same
//  element in unsynchronised manner.
//
//  T is the type of the object in the queue.
//  N is granularity of the queue (how many pushes have to be done till
//  actual memory allocation is required).
//
// ALIGN is the memory alignment size to use in the case where we have
// posix_memalign available. Default value is 64, this alignment will
// prevent two queue chunks from occupying the same CPU cache line on
// architectures where cache lines are <= 64 bytes (e.g. most things
// except POWER). It is detected at build time to try to account for other
// platforms like POWER and s390x.
template <typename T, int N, size_t ALIGN = 64> 
class yqueue_t
{
  public:
    inline yqueue_t ()
    {
        _begin_chunk = allocate_chunk ();
        alloc_assert (_begin_chunk);
        _begin_pos = 0;
        _back_chunk = NULL;
        _back_pos = 0;
        _end_chunk = _begin_chunk;
        _end_pos = 0;
    }
    inline ~yqueue_t ()
    {
        while (true) {
            if (_begin_chunk == _end_chunk) {
                free (_begin_chunk);
                break;
            }
            chunk_t *o = _begin_chunk;
            _begin_chunk = _begin_chunk->next;
            free (o);
        }
        chunk_t *sc = _spare_chunk.xchg (NULL);
        free (sc);
    }
    inline T &front () { return _begin_chunk->values[_begin_pos]; }
    inline T &back () { return _back_chunk->values[_back_pos]; }
    inline void push ()
    {
        _back_chunk = _end_chunk;
        _back_pos = _end_pos;

        if (++_end_pos != N)
            return;

        chunk_t *sc = _spare_chunk.xchg (NULL);
        if (sc) {
            _end_chunk->next = sc;
            sc->prev = _end_chunk;
        } else {
            _end_chunk->next = allocate_chunk ();
            alloc_assert (_end_chunk->next);
            _end_chunk->next->prev = _end_chunk;
        }
        _end_chunk = _end_chunk->next;
        _end_pos = 0;
    }

    //  Removes element from the back end of the queue. In other words
    //  it rollbacks last push to the queue. Take care: Caller is
    //  responsible for destroying the object being unpushed.
    //  The caller must also guarantee that the queue isn't empty when
    //  unpush is called. It cannot be done automatically as the read
    //  side of the queue can be managed by different, completely
    //  unsynchronised thread.
    inline void unpush ()
    {
        if (_back_pos)
            --_back_pos;
        else {
            _back_pos = N - 1;
            _back_chunk = _back_chunk->prev;
        }
        if (_end_pos)
            --_end_pos;
        else {
            _end_pos = N - 1;
            _end_chunk = _end_chunk->prev;
            free (_end_chunk->next);
            _end_chunk->next = NULL;
        }
    }
    inline void pop ()
    {
        if (++_begin_pos == N) {
            chunk_t *o = _begin_chunk;
            _begin_chunk = _begin_chunk->next;
            _begin_chunk->prev = NULL;
            _begin_pos = 0;
            chunk_t *cs = _spare_chunk.xchg (o);
            free (cs);
        }
    }

  private:
    struct chunk_t
    {
        T values[N];
        chunk_t *prev;
        chunk_t *next;
    };
    static inline chunk_t *allocate_chunk ()
    {
        void *pv;
        if (posix_memalign (&pv, ALIGN, sizeof (chunk_t)) == 0)
            return (chunk_t *) pv;
        return NULL;
    }

    //  Back position may point to invalid memory if the queue is empty,
    //  while begin & end positions are always valid. Begin position is
    //  accessed exclusively be queue reader (front/pop), while back and
    //  end positions are accessed exclusively by queue writer (back/push).
    chunk_t *_begin_chunk;
    int _begin_pos;
    chunk_t *_back_chunk;
    int _back_pos;
    chunk_t *_end_chunk;
    int _end_pos;

    //  People are likely to produce and consume at similar rates.  In
    //  this scenario holding onto the most recently freed chunk saves
    //  us from having to call malloc/free.
    atomic_ptr_t<chunk_t> _spare_chunk;

    yqueue_t (const yqueue_t &) = delete;
    yqueue_t &operator= (const yqueue_t &) = delete;
    yqueue_t (yqueue_t &&) = delete;
    yqueue_t &operator= (yqueue_t &&) = delete;
};

● 无锁队列

write(T, true)相当于没写入,write(T, false)相当于写入了,此时flush才奏效,unwrite只对write(T,false)且flush之前才有用

//  Lock-free queue implementation.
//  Only a single thread can read from the pipe at any specific moment.
//  Only a single thread can write to the pipe at any specific moment.
//  T is the type of the object in the queue.
//  N is granularity of the pipe, i.e. how many items are needed to
//  perform next memory allocation.

template <typename T, int N> class ypipe_t ZMQ_FINAL : public ypipe_base_t<T>
{
  public:
    //  Initialises the pipe.
    ypipe_t ()
    {
        //  Insert terminator element into the queue.
        _queue.push ();

        //  Let all the pointers to point to the terminator.
        //  (unless pipe is dead, in which case c is set to NULL).
        _r = _w = _f = &_queue.back ();
        _c.set (&_queue.back ());
    }
    //  Write an item to the pipe.  Don't flush it yet. If incomplete is
    //  set to true the item is assumed to be continued by items
    //  subsequently written to the pipe. Incomplete items are never
    //  flushed down the stream.
    void write (const T &value_, bool incomplete_)
    {
        //  Place the value to the queue, add new terminator element.
        _queue.back () = value_;
        _queue.push ();

        //  Move the "flush up to here" pointer.
        if (!incomplete_)
            _f = &_queue.back ();
    }
    //  Pop an incomplete item from the pipe. Returns true if such
    //  item exists, false otherwise.
    bool unwrite (T *value_)
    {
        if (_f == &_queue.back ())
            return false;
        _queue.unpush ();
        *value_ = _queue.back ();
        return true;
    }

    //  Flush all the completed items into the pipe. Returns false if
    //  the reader thread is sleeping. In that case, caller is obliged to
    //  wake the reader up before using the pipe again.
    bool flush ()
    {
        //  If there are no un-flushed items, do nothing.
        if (_w == _f)
            return true;

        //  Try to set 'c' to 'f'.  如果c和w相等则c = f, 返回w且值不变
        // 如果c和w相等则c
        if (_c.cas (_w, _f) != _w) {
            //  Compare-and-swap was unsuccessful because 'c' is NULL.
            //  This means that the reader is asleep. Therefore we don't
            //  care about thread-safeness and update c in non-atomic
            //  manner. We'll return false to let the caller know
            //  that reader is sleeping.
            _c.set (_f);
            _w = _f;
            return false;
        }

        //  Reader is alive. Nothing special to do now. Just move
        //  the 'first un-flushed item' pointer to 'f'.
        _w = _f;
        return true;
    }

    //  Check whether item is available for reading.
    bool check_read ()
    {
        //  Was the value prefetched already? If so, return.
        if (&_queue.front () != _r && _r)
            return true;

        //  There's no prefetched value, so let us prefetch more values.
        //  Prefetching is to simply retrieve the
        //  pointer from c in atomic fashion. If there are no
        //  items to prefetch, set c to NULL (using compare-and-swap).
        _r = _c.cas (&_queue.front (), NULL);

        //  If there are no elements prefetched, exit.
        //  During pipe's lifetime r should never be NULL, however,
        //  it can happen during pipe shutdown when items
        //  are being deallocated.
        if (&_queue.front () == _r || !_r)
            return false;

        //  There was at least one value prefetched.
        return true;
    }

    //  Reads an item from the pipe. Returns false if there is no value.
    //  available.
    bool read (T *value_)
    {
        //  Try to prefetch a value.
        if (!check_read ())
            return false;

        //  There was at least one value prefetched.
        //  Return it to the caller.
        *value_ = _queue.front ();
        _queue.pop ();
        return true;
    }

    //  Applies the function fn to the first element in the pipe
    //  and returns the value returned by the fn.
    //  The pipe mustn't be empty or the function crashes.
    bool probe (bool (*fn_) (const T &))
    {
        const bool rc = check_read ();
        zmq_assert (rc);

        return (*fn_) (_queue.front ());
    }

  protected:
    //  Allocation-efficient queue to store pipe items.
    //  Front of the queue points to the first prefetched item, back of
    //  the pipe points to last un-flushed item. Front is used only by
    //  reader thread, while back is used only by writer thread.
    yqueue_t<T, N> _queue;  // 存放T数据的实体

    //  Points to the first un-flushed item. This variable is used
    //  exclusively by writer thread.
    T *_w;

    //  Points to the first un-prefetched item. This variable is used
    //  exclusively by reader thread.
    T *_r;

    //  Points to the first item to be flushed in the future.
    T *_f;

    //  The single point of contention between writer and reader thread.
    //  Points past the last flushed item. If it is NULL,
    //  reader is asleep. This pointer should be always accessed using
    //  atomic operations.
    atomic_ptr_t<T> _c;  // _c指向 T*

    ZMQ_NON_COPYABLE_NOR_MOVABLE (ypipe_t)
};

总结

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值