環形內存circular_buffer

boost中支持環形內存。該內存在一些地方還是蠻實用的。

簡單看下具體使用及部分源碼,使用和源碼相對來說都還是比較簡單,易於理解的

與STL接口基本一致


void CCircularBufferTest::TestCircularBuffer()
{
 circular_buffer<CTest> aa(5);  // 預先分配5個sizeof(CTest)的大小空間
 aa.push_back(CTest(1)); // 插入元素
 aa.push_back(CTest(2));
 int isize = aa.size();
 bool bfull = aa.full();

 for (int i = 3; i <= 7; ++i)
 {
  aa.push_back(CTest(i));  // 再次插入會覆蓋到最初進入的元素
 }
 CTest* pTest = aa.linearize();  // 轉換爲指針
 CTest* pTest5 = pTest+4;
}

部分源碼分析

構造函數

explicit circular_buffer(capacity_type buffer_capacity, const allocator_type& alloc = allocator_type())
    : m_size(0), m_alloc(alloc) {
        initialize_buffer(buffer_capacity);
        m_first = m_last = m_buff;
    }

保存能力,默認分配算法

裏面有m_first,m_last,m_buffer保存着該環形內存的各個位置狀態

初始化該能力的空間

//! Initialize the internal buffer.
    void initialize_buffer(capacity_type buffer_capacity) {
        m_buff = allocate(buffer_capacity);
        m_end = m_buff + buffer_capacity;
    }

繼續往下調用allocate。再往下就是調用::operator new,沒有調用C++的new,因爲此處是分配原始內存,沒有構造函數的

//! Allocate memory.
    pointer allocate(size_type n) {
        if (n > max_size())
            throw_exception(std::length_error("circular_buffer"));
#if BOOST_CB_ENABLE_DEBUG
        pointer p = (n == 0) ? 0 : m_alloc.allocate(n);
        cb_details::do_fill_uninitialized_memory(p, sizeof(value_type) * n);
        return p;
#else
        return (n == 0) ? 0 : m_alloc.allocate(n);
#endif
    }

這個裏面有個比較有意思的地方do_fill_uninitialized_memory

const int UNINITIALIZED = 0xcc;

template <class T>
inline void do_fill_uninitialized_memory(T* data, std::size_t size_in_bytes) BOOST_NOEXCEPT {
    std::memset(static_cast<void*>(data), UNINITIALIZED, size_in_bytes);
}

是對這個地址空間memset爲0xcc,就是我們經常看到的燙燙。。。,是爲了方便我們一眼就看出這個內存是未初始化賦值的


構造後再看下push_back操作

template <class ValT>
    void push_back_impl(ValT item) {
        if (full()) {
            if (empty())
                return;
            replace(m_last, static_cast<ValT>(item));
            increment(m_last);
            m_first = m_last;
        } else {
            boost::container::allocator_traits<Alloc>::construct(m_alloc, boost::addressof(*m_last), static_cast<ValT>(item));
            increment(m_last);
            ++m_size;
        }       
    }

如果空間未滿,就會調用construct。實際上最後是調用到了

inline void *operator new(std::size_t, void *p, boost_container_new_t)

在p地址空間上調用了T的構造函數,我這裏使用的是一個類CTest,最後會調用到CTest的拷貝構造函數中。

如果空間已經滿了,則把最前面的進行覆蓋,即上面的m_pLast,再看下last的賦值就知道爲什麼需要覆蓋last了

 void increment(Pointer& p) const {
        if (++p == m_end)
            p = m_buff;
    }

當p的值到達m_pend時,會把m_buff(即首地址)賦給p,而p其實只是個參數,傳入的就是m_last。就是滿了的話,m_last就指向了最前面了

最主要的應該就是上面分析的這幾個接口了。


可以看出環形內存在一開始就分配了固定大小的內存出來,有時或許會浪費空間,所以boost還提供了一個circular_buffer_space_optimized的優化型的緩衝區

先看下基本使用

void CCircularBufferTest::TestCircularBufferSpaceOptimized()
{
 circular_buffer_space_optimized<CTest> cb(10);
 for (int i = 0; i < 15; ++i)
 {
  cb.push_back(CTest(i));
 }
 int icbSize = cb.size();
 int icbCapacity = cb.capacity();

 TestCircularBufferSpaceOptimized();
}


接口基本一致,只是源碼實現會略有不同

構造函數

explicit circular_buffer_space_optimized(capacity_type capacity_ctrl,
        const allocator_type& alloc = allocator_type())
    : circular_buffer<T, Alloc>(capacity_ctrl.min_capacity(), alloc)
    , m_capacity_ctrl(capacity_ctrl) {}

裏面會用到一個輔助類


template <class Size>
class capacity_control {

    //! The capacity of the space-optimized circular buffer.
    Size m_capacity;

    //! The lowest guaranteed or minimum capacity of the adapted space-optimized circular buffer.
    Size m_min_capacity;

public:

    //! Constructor.
    capacity_control(Size buffer_capacity, Size min_buffer_capacity = 0)
    : m_capacity(buffer_capacity), m_min_capacity(min_buffer_capacity)
    { // Check for capacity lower than min_capacity.
        BOOST_CB_ASSERT(buffer_capacity >= min_buffer_capacity);
    }

    // Default copy constructor.

    // Default assign operator.

    //! Get the capacity of the space optimized circular buffer.
    Size capacity() const { return m_capacity; }

    //! Get the minimal capacity of the space optimized circular buffer.
    Size min_capacity() const { return m_min_capacity; }

    //! Size operator - returns the capacity of the space optimized circular buffer.
    operator Size() const { return m_capacity; }
};

circular_buffer_space_optimized的容量是用該結構體在表示,它的構造只是賦了一些初始化值,而不會分配內存

再看下push_back的實現

void push_back(param_value_type item) {
        check_low_capacity();
        circular_buffer<T, Alloc>::push_back(item);
    }

後面一句circular_buffer<T, Alloc>::push_back(item);與上面的circular_buffer一樣。區別就在於check_low_capacity了

void check_low_capacity(size_type n = 1) {
        size_type new_size = size() + n;
        size_type new_capacity = circular_buffer<T, Alloc>::capacity();
        if (new_size > new_capacity) {
            if (new_capacity == 0)
                new_capacity = 1;
            for (; new_size > new_capacity; new_capacity *= 2) {}
            circular_buffer<T, Alloc>::set_capacity(
                ensure_reserve(new_capacity, new_size));
        }
#if BOOST_CB_ENABLE_DEBUG
        this->invalidate_iterators_except(end());
#endif
    }

該接口會判斷當前需要的大小,就是以前的大小加上n,因爲push_back是插入一個元素,所以n用的默認值1,如果是insert多個元素,此處會傳入有效的n

new_size>new_capacity,就表示空間不夠了。需要新分配,新分配使用circular_buffer,會進行*2擴容分配( for (; new_size > new_capacity; new_capacity *= 2) {})。這樣可以減少分配次數。當然擴容分配的容量也是在最初指定的能力之內的。此處有判斷,如下
    //! Ensure the reserve for possible growth up.
    size_type ensure_reserve(size_type new_capacity, size_type buffer_size) const {
        if (buffer_size + new_capacity / 5 >= new_capacity)
            new_capacity *= 2; // ensure at least 20% reserve
        if (new_capacity > m_capacity_ctrl)
            return m_capacity_ctrl;
        return new_capacity;
    }

m_capacity_ctrl就是最初指定的值,如果*2擴容之後,不能保證空餘20%,會再次*2擴容。空間擴展後,後續push_back就與前面一樣了

另外它還有一個優化就是erase後,它會刪除空間???? erase源碼分析

iterator erase(iterator pos) {
        iterator it = circular_buffer<T, Alloc>::erase(pos);
        size_type index = it - begin();
        check_high_capacity();
        return begin() + index;
    }

circular_buffer<T, Alloc>::erase(pos);只是會調用對象的析構函數,不會釋放空間


與書中講解衝突的兩個地方

1、優化後的環形內存分配足空間後是不會釋放的

2、優化後的環形內存,在分配空間時會大於非優化的環形內存。比如我只需要500個2k的數據,用非優化的會分配1M的空間出來,但是用了優化個的circular_buffer_space_optimized會分配2M,看源碼就能發現原因所在了。使用需要注意呀,羅劍鋒的boost中有些地方講解的不太對呀。





發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章