Bootstrap

C++编程:实现一个基于原始指针的环形缓冲区(RingBuffer)缓存串口数据

0. 引言

本文将介绍一个解析串口数据的环形缓冲区,使用’$‘或者’#'分割串口消息。

主要设计考虑包括:

  1. 内存管理:选用内存块数组,而不是选用一整块的内存+读写指针的方式,以减少读写数据时的资源竞争。使用原始指针手动分配和释放内存,确保每个 Block 管理自己的缓冲区。
    确保资源的安全转移。
  2. 状态管理:每个 Block 通过 status 成员变量表示其当前状态(空闲、使用中或已使用)。

1. 使用示例

以下是一个简单的使用示例,展示如何创建 RingBuffer 实例、追加数据、访问当前使用的 Block 以及释放 Block

#include "ring_buffer.hpp"

int main()
{
    try
    {
        // 创建一个包含10个Block的RingBuffer
        RingBuffer ringBuffer(10);

        // 追加数据1
        const char* data1 = "Hello, World!";
        if (!ringBuffer.append(reinterpret_cast<const uint8_t*>(data1), std::strlen(data1)))
        {
            std::cerr << "Failed to append data1.\n";
        }

        // 追加数据2,包含特殊字符
        const char* data2 = "Special#Character";
        if (!ringBuffer.append(reinterpret_cast<const uint8_t*>(data2), std::strlen(data2)))
        {
            std::cerr << "Failed to append data2.\n";
        }

        // 访问当前使用的Block
        const RingBuffer::Block& usedBlock = ringBuffer.currentUsed();
        std::cout << "Used Block Index: " << usedBlock.index << ", Written: " << usedBlock.written << "\n";

        // 访问当前正在写入的Block
        const RingBuffer::Block& usingBlock = ringBuffer.currentUsing();
        std::cout << "Using Block Index: " << usingBlock.index << ", Written: " << usingBlock.written << "\n";

        // 释放第一个Block
        ringBuffer.release(0);
    }
    catch (const std::exception& ex)
    {
        std::cerr << "Exception: " << ex.what() << "\n";
    }

    return 0;
}

解析:

  1. 创建 RingBuffer:初始化一个包含10个 BlockRingBuffer 实例。
  2. 追加数据
    • 追加字符串 "Hello, World!"
    • 追加包含特殊字符 "#" 的字符串 "Special#Character",触发数据分割逻辑。
  3. 访问 Block
    • 获取并打印当前使用的 Block 的索引和已写入字节数。
    • 获取并打印当前正在写入的 Block 的索引和已写入字节数。
  4. 释放 Block:释放第一个 Block,将其状态标记为 BufFree 并重置已写入字节数。
  5. 异常处理:使用 try-catch 块捕获并处理可能抛出的异常,确保程序的稳定性。

2. 流程图

2.1 追加数据流程

首先,我们来看一下追加数据的整体流程。这是环形缓冲区的核心功能,负责将新数据添加到缓冲区中。

长度超过
长度合适
有足够空间且状态为BufUsing
无足够空间或状态不符
获取成功
获取失败
开始
验证数据长度
输出错误信息
检查当前块空间和状态
数据复制到当前块
尝试获取下一个空闲块
设置新块状态为BufUsing
输出错误信息
结束

流程图解析

  1. 开始:数据追加操作的起点。
  2. 验证数据长度:检查待追加的数据是否超过单个块的大小(BlockSize)。
    • 长度超过:输出错误信息,终止操作。
    • 长度合适:继续下一步。
  3. 检查当前块空间和状态:判断当前块是否有足够的空间且状态为BufUsing(使用中)。
    • 有足够空间且状态正确:将数据复制到当前块。
    • 无足够空间或状态不符:尝试获取下一个空闲块。
  4. 尝试获取下一个空闲块
    • 获取成功:设置新块的状态为BufUsing,继续数据追加。
    • 获取失败:输出错误信息,终止操作。
  5. 结束:数据追加流程结束。

2.2 获取空闲块流程

当当前块空间不足或状态不符时,需要获取下一个空闲块。以下流程图展示了获取空闲块的具体步骤。

flowchart TD
    A[开始] --> B[检查当前块状态]
    B --> |状态为BufFree| C[可以使用当前块]
    B --> |状态不为BufFree| D[检查是否允许重试]
    D --> |允许重试| E[返回失败]
    D --> |不允许重试| F[标记当前块为BufUsed]
    F --> G[检查是否到达缓冲区末尾]
    G --> |是| H[回绕到第一个块]
    G --> |否| I[移动到下一个块]
    H --> J[检查块是否为空]
    I --> J
    J --> |块为空| K[成功获取空闲块]
    J --> |块不为空| L[输出警告,返回失败]
    C --> K
    K --> M[结束]
    L --> M
    E --> M

流程图解析

  1. 开始:获取空闲块操作的起点。
  2. 检查当前块状态:判断当前块是否为空闲状态(BufFree)。
    • 状态为BufFree:可以直接使用当前块。
    • 状态不为BufFree:进一步检查是否允许重试。
  3. 检查是否允许重试
    • 允许重试:直接返回失败,不进行进一步操作。
    • 不允许重试:将当前块标记为BufUsed,表示已被使用。
  4. 标记当前块为BufUsed:更新当前块的状态。
  5. 检查是否到达缓冲区末尾
    • :回绕到缓冲区的第一个块。
    • :移动到下一个块。
  6. 检查块是否为空:判断目标块是否为空闲状态。
    • 块为空:成功获取空闲块。
    • 块不为空:输出警告信息,返回失败。
  7. 结束:获取空闲块流程结束。

2.3 处理特殊字符流程

在追加数据过程中,如果数据中包含特殊字符(如#$),需要对数据进行分割处理。以下流程图展示了处理特殊字符的具体步骤。

不包含
包含
获取成功
获取失败
开始
检查数据中是否包含特殊字符
复制整个数据到当前块
复制特殊字符前的数据到当前块
尝试获取新块
设置新块状态为BufUsing
输出错误信息
复制特殊字符到新块
检查是否有剩余数据
复制剩余数据到新块
结束

流程图解析

  1. 开始:处理特殊字符操作的起点。
  2. 检查数据中是否包含特殊字符:扫描数据是否包含#$
    • 不包含:直接将整个数据复制到当前块。
    • 包含:将特殊字符前的数据复制到当前块。
  3. 复制特殊字符前的数据到当前块:部分数据复制到当前块。
  4. 尝试获取新块
    • 获取成功:设置新块状态为BufUsing
    • 获取失败:输出错误信息,终止操作。
  5. 设置新块状态为BufUsing:更新新块的状态。
  6. 复制特殊字符到新块:将特殊字符复制到新块。
  7. 检查是否有剩余数据
    • :将剩余数据复制到新块。
    • :结束操作。
  8. 结束:处理特殊字符流程结束。

2.4 释放块流程

当某个块的数据被处理完毕后,需要将其释放,以便后续数据的追加。以下流程图展示了释放块的具体步骤。

有效
无效
开始
检查块索引是否有效
重置写入字节数
设置块状态为BufFree
结束
输出错误信息

流程图解析

  1. 开始:释放块操作的起点。
  2. 检查块索引是否有效:验证要释放的块索引是否在有效范围内。
    • 有效:继续释放流程。
    • 无效:输出错误信息,终止操作。
  3. 重置写入字节数:将块中的written成员变量重置为0
  4. 设置块状态为BufFree:将块的状态更新为BufFree,表示该块已空闲。
  5. 结束:释放块流程结束。

2.5 获取下一个使用块流程

在某些情况下,需要获取下一个已经使用的块。以下流程图展示了获取下一个使用块的具体步骤。

状态为BufUsed
状态不为BufUsed
状态为BufUsed
状态不为BufUsed
状态为BufUsed
状态不为BufUsed
开始
检查当前使用块的状态
返回当前使用块的索引
检查是否到达缓冲区末尾
检查第一个块的状态
检查下一个块的状态
更新使用块索引为0
返回-1
更新使用块索引为下一个块
返回使用块的索引
结束

流程图解析

  1. 开始:获取下一个使用块操作的起点。
  2. 检查当前使用块的状态:判断当前indexUsed_指向的块是否为BufUsed状态。
    • 状态为BufUsed:返回当前使用块的索引。
    • 状态不为BufUsed:继续检查是否需要回绕。
  3. 检查是否到达缓冲区末尾
    • :检查第一个块的状态。
    • :检查下一个块的状态。
  4. 检查第一个块的状态
    • 状态为BufUsed:更新indexUsed_0,返回索引。
    • 状态不为BufUsed:返回-1,表示没有找到下一个使用块。
  5. 检查下一个块的状态
    • 状态为BufUsed:更新indexUsed_为下一个块的索引,返回索引。
    • 状态不为BufUsed:返回-1,表示没有找到下一个使用块。
  6. 结束:获取下一个使用块流程结束。

3. 代码详解

下面我们将逐步解析 RingBuffer 类的实现,理解其内部工作机制。

3.1 Block 结构体

struct Block
{
    uint8_t* buf;     ///< Buffer to store data.
    int8_t   status;  ///< Status of the block (free, using, or used).
    int32_t  index;   ///< Index of the block within the ring buffer.
    uint32_t written; ///< Number of bytes written to the block.

    /**
     * @brief Constructs a Block with a pre-allocated buffer.
     *
     * Initializes the buffer and sets default values for other members.
     */
    Block() : buf(new uint8_t[BlockSize]), status(BufFree), index(-1), written(0)
    {
        // Initialize buffer to zero (optional)
        std::memset(buf, 0, BlockSize);
    }

    /**
     * @brief Destructor to deallocate the buffer.
     */
    ~Block() { delete[] buf; }

    // Delete copy constructor and copy assignment operator to prevent shallow copies.
    Block(const Block&)            = delete;
    Block& operator=(const Block&) = delete;

    // Define move constructor and move assignment operator for safe transfers.
    Block(Block&& other) noexcept : buf(other.buf), status(other.status), index(other.index), written(other.written)
    {
        other.buf     = nullptr;
        other.status  = BufFree;
        other.index   = -1;
        other.written = 0;
    }

    Block& operator=(Block&& other) noexcept
    {
        if (this != &other)
        {
            delete[] buf;

            buf     = other.buf;
            status  = other.status;
            index   = other.index;
            written = other.written;

            other.buf     = nullptr;
            other.status  = BufFree;
            other.index   = -1;
            other.written = 0;
        }
        return *this;
    }

    static constexpr uint32_t BlockSize = 256; ///< Size of each block in bytes.
    static constexpr int8_t   BufFree   = 0;   ///< Indicates the block is free.
    static constexpr int8_t   BufUsing  = 1;   ///< Indicates the block is currently in use.
    static constexpr int8_t   BufUsed   = 2;   ///< Indicates the block has been used.
};

解析:

  • 成员变量:

    • uint8_t* buf:指向数据缓冲区的原始指针。
    • int8_t status:表示 Block 的当前状态,BufFree(空闲)、BufUsing(使用中)、BufUsed(已使用)。
    • int32_t indexBlockRingBuffer 中的索引。
    • uint32_t written:已写入 Block 的字节数。
  • 构造函数与析构函数:

    • 构造函数中,使用 new 分配固定大小的缓冲区,并初始化其他成员变量。
    • 析构函数中,使用 delete[] 释放缓冲区内存。
  • 拷贝控制:

    • 拷贝构造函数和拷贝赋值运算符被删除,防止浅拷贝带来的内存管理问题。
    • 移动构造函数和移动赋值运算符 被定义,允许 Block 实例的资源安全转移。

3.2 RingBuffer 类

class RingBuffer
{
  public:
    // Block 结构体定义...

    /**
     * @brief Constructs a RingBuffer with a specified number of blocks.
     *
     * @param num The number of blocks in the ring buffer. Defaults to 1000.
     */
    explicit RingBuffer(uint32_t num = 1000) : blocks_(num), blockNum_(num), index_(0), indexUsed_(-1)
    {
        // Initialize each block's index.
        for (uint32_t i = 0; i < blocks_.size(); ++i)
        {
            blocks_[i].index = static_cast<int32_t>(i);
        }
        // Set the first block's status to 'using'.
        if (!blocks_.empty())
        {
            blocks_[0].status = Block::BufUsing;
        }
    }

    /**
     * @brief Destructor to deallocate all blocks.
     */
    ~RingBuffer() = default; // Blocks are automatically destroyed, and their destructors handle buffer deallocation.

    // Delete copy constructor and copy assignment operator to prevent shallow copies.
    RingBuffer(const RingBuffer&)            = delete;
    RingBuffer& operator=(const RingBuffer&) = delete;

    // Define move constructor and move assignment operator for safe transfers.
    RingBuffer(RingBuffer&& other) noexcept
        : blocks_(std::move(other.blocks_)),
          blockNum_(other.blockNum_),
          index_(other.index_),
          indexUsed_(other.indexUsed_)
    {
        other.blockNum_  = 0;
        other.index_     = 0;
        other.indexUsed_ = -1;
    }

    RingBuffer& operator=(RingBuffer&& other) noexcept
    {
        if (this != &other)
        {
            blocks_    = std::move(other.blocks_);
            blockNum_  = other.blockNum_;
            index_     = other.index_;
            indexUsed_ = other.indexUsed_;

            other.blockNum_  = 0;
            other.index_     = 0;
            other.indexUsed_ = -1;
        }
        return *this;
    }

    // append 方法定义...

    // currentUsed 和 currentUsing 方法定义...

    // release 方法定义...

    // nextUsed 方法定义...

  private:
    // nextFree 和 checkSpecial 方法定义...

    std::vector<Block> blocks_;         ///< Vector of blocks managed by the ring buffer.
    int32_t            index_     = 0;  ///< Current index for writing data.
    int32_t            blockNum_  = 0;  ///< Total number of blocks in the ring buffer.
    int32_t            indexUsed_ = -1; ///< Index of the currently used block.
};

解析:

  • 成员变量:

    • std::vector<Block> blocks_:存储所有 Block 的容器。
    • int32_t index_:当前用于写入数据的 Block 索引。
    • int32_t blockNum_:环形缓冲区中 Block 的总数。
    • int32_t indexUsed_:当前正在使用的 Block 索引,初始化为 -1 表示无 Block 被使用。
  • 构造函数与析构函数:

    • 构造函数中,初始化 blocks_ 向量,并为每个 Block 设置其索引。默认情况下,第一个 Block 的状态设置为 BufUsing,表示其已被使用。
    • 析构函数使用默认实现,依赖于 Block 的析构函数自动释放内存。
  • 拷贝控制:

    • 拷贝构造函数和拷贝赋值运算符被删除,防止浅拷贝导致的资源管理问题。
    • 移动构造函数和移动赋值运算符 被定义,允许 RingBuffer 实例的资源安全转移。

3.3 主要方法解析

append 方法
/**
 * @brief Appends data to the ring buffer.
 *
 * This method attempts to append the provided data to the current block.
 * If there's insufficient space or a retry is requested, it tries to acquire the next free block.
 * It also handles splitting data when special characters are encountered.
 *
 * @param buf Pointer to the data to append.
 * @param length Length of the data in bytes.
 * @param retry Flag indicating whether to retry acquiring a free block.
 * @return True if the data was successfully appended; false otherwise.
 */
bool append(const uint8_t* buf, uint32_t length, bool retry = false)
{
    // Validate that the data length does not exceed block size.
    if (length > Block::BlockSize)
    {
        std::cerr << "Error: Length " << length << " exceeds block size " << Block::BlockSize << ".\n";
        return false;
    }

    // Check if the current block has enough space and is in the correct status.
    if (!retry && blocks_[index_].written + length <= Block::BlockSize)
    {
        if (blocks_[index_].status != Block::BufUsing)
        {
            std::cerr << "Error: Current block status is not BufUsing.\n";
            return false;
        }
    }
    else
    {
        // Attempt to acquire the next free block.
        if (!nextFree(retry))
        {
            // If acquiring a free block fails, ensure the current block status is correct.
            if (blocks_[index_].status != Block::BufUsed)
            {
                std::cerr << "Error: Current block status is not BufUsed.\n";
            }
            return false;
        }
        // Update the status of the newly acquired block.
        blocks_[index_].status = Block::BufUsing;
    }

    // If the current block is empty, copy the entire buffer.
    if (blocks_[index_].written == 0)
    {
        std::memcpy(blocks_[index_].buf + blocks_[index_].written, buf, length);
        blocks_[index_].written += length;
    }
    else
    {
        // Check for special characters in the buffer.
        int specialIdx = checkSpecial(buf, length);
        if (specialIdx == -1)
        {
            // No special characters found; attempt to copy the entire buffer.
            if (blocks_[index_].written + length > Block::BlockSize)
            {
                std::cerr << "Error: Not enough space to append data without special character.\n";
                return false;
            }
            std::memcpy(blocks_[index_].buf + blocks_[index_].written, buf, length);
            blocks_[index_].written += length;
        }
        else
        {
            // Special character found; split the data at the special character.
            if (blocks_[index_].written + specialIdx > Block::BlockSize)
            {
                std::cerr << "Error: Not enough space to copy data before special character.\n";
                return false;
            }
            // Copy data up to the special character.
            std::memcpy(blocks_[index_].buf + blocks_[index_].written, buf, specialIdx);
            blocks_[index_].written += specialIdx;

            // Attempt to acquire a new free block for the special character.
            if (!nextFree(retry))
            {
                if (blocks_[index_].status != Block::BufUsed)
                {
                    std::cerr << "Error: Current block status is not BufUsed after finding special character.\n";
                }
                return false;
            }

            // Update the status and reset the write position for the new block.
            blocks_[index_].status  = Block::BufUsing;
            blocks_[index_].written = 0;

            // Copy the special character to the new block.
            blocks_[index_].buf[blocks_[index_].written++] = buf[specialIdx];

            // If there is remaining data after the special character, copy it to the new block.
            if (static_cast<uint32_t>(specialIdx + 1) < length)
            {
                uint32_t remainingLength = length - specialIdx - 1;
                if (remainingLength > Block::BlockSize - blocks_[index_].written)
                {
                    std::cerr << "Error: Not enough space to copy remaining data after special character.\n";
                    return false;
                }
                std::memcpy(blocks_[index_].buf + blocks_[index_].written, buf + specialIdx + 1, remainingLength);
                blocks_[index_].written += remainingLength;
            }
        }
    }
    return true;
}

解析:

  • 功能:向环形缓冲区追加数据。
  • 步骤
    1. 长度校验:确保待追加的数据长度不超过单个 Block 的大小。
    2. 空间与状态检查:检查当前 Block 是否有足够的空间以及其状态是否为 BufUsing
    3. 获取新块:如果当前块空间不足或请求重试,尝试获取下一个空闲块。
    4. 数据复制
      • 如果当前块为空,直接复制整个数据。
      • 如果当前块已有数据,检查数据中是否包含特殊字符(#$)。
        • 无特殊字符:直接复制数据。
        • 有特殊字符:在特殊字符处分割数据,分别复制到当前块和新获取的块中。
currentUsed 和 currentUsing 方法
/**
 * @brief Retrieves the currently used block.
 *
 * @return A constant reference to the currently used block.
 * @throws std::out_of_range if the indexUsed_ is invalid.
 */
const Block& currentUsed() const
{
    if (indexUsed_ >= 0 && static_cast<size_t>(indexUsed_) < blocks_.size())
    {
        return blocks_[indexUsed_];
    }
    throw std::out_of_range("currentUsed(): indexUsed_ is out of range.");
}

/**
 * @brief Retrieves the block that is currently being used for writing.
 *
 * @return A constant reference to the block currently in use.
 * @throws std::out_of_range if the index_ is invalid.
 */
const Block& currentUsing() const
{
    if (static_cast<size_t>(index_) < blocks_.size())
    {
        return blocks_[index_];
    }
    throw std::out_of_range("currentUsing(): index_ is out of range.");
}

解析:

  • 功能
    • currentUsed():获取当前正在使用的 Block
    • currentUsing():获取当前用于写入的 Block
  • 实现
    • 通过索引 indexUsed_index_ 访问 blocks_ 向量中的相应 Block
    • 添加了边界检查,若索引无效,抛出 std::out_of_range 异常,防止访问越界。
release 方法
/**
 * @brief Releases a block, marking it as free and resetting its written bytes.
 *
 * @param idx The index of the block to release.
 */
void release(uint32_t idx)
{
    if (idx < blocks_.size())
    {
        blocks_[idx].written = 0;
        blocks_[idx].status  = Block::BufFree;
    }
    else
    {
        std::cerr << "Error: Release index " << idx << " is out of bounds.\n";
    }
}

解析:

  • 功能:释放指定索引的 Block,将其状态标记为 BufFree 并重置已写入字节数。
  • 实现
    • 检查索引是否在有效范围内。
    • 更新 Blockwrittenstatus 成员。
nextUsed 方法
/**
 * @brief Retrieves the next used block's index.
 *
 * @return The index of the next used block, or -1 if no such block exists.
 */
int32_t nextUsed()
{
    // Check if the current used index is valid and marked as used.
    if (indexUsed_ >= 0 && static_cast<size_t>(indexUsed_) < blocks_.size() &&
        blocks_[indexUsed_].status == Block::BufUsed)
    {
        return indexUsed_;
    }

    // Handle wrapping around to the beginning of the ring buffer.
    if (indexUsed_ == static_cast<int32_t>(blockNum_) - 1)
    {
        if (!blocks_.empty() && blocks_[0].status == Block::BufUsed)
        {
            indexUsed_ = 0;
            return indexUsed_;
        }
        else
        {
            return -1;
        }
    }
    else
    {
        // Check the next block in sequence.
        if ((indexUsed_ + 1) < static_cast<int32_t>(blockNum_) && blocks_[indexUsed_ + 1].status == Block::BufUsed)
        {
            indexUsed_ += 1;
            return indexUsed_;
        }
        else
        {
            return -1;
        }
    }
}

解析:

  • 功能:获取下一个已使用的 Block 的索引。
  • 实现
    • 检查当前 indexUsed_ 是否指向一个已使用的 Block
    • 如果在缓冲区末尾,尝试回绕到起始位置。
    • 否则,检查下一个 Block 是否已使用。
    • 若未找到,则返回 -1

3.4 私有方法解析

nextFree 方法
/**
 * @brief Attempts to acquire the next free block in the ring buffer.
 *
 * @param retry Flag indicating whether to retry acquiring a free block.
 * @return True if a free block was successfully acquired; false otherwise.
 */
bool nextFree(bool retry = false)
{
    // If the current block is free, it can be used.
    if (blocks_[index_].status == Block::BufFree)
    {
        return true;
    }
    else
    {
        // If retry is requested and the block is not free, do not attempt further.
        if (retry)
        {
            return false;
        }
    }

    // Mark the current block as used since it cannot be reused immediately.
    blocks_[index_].status = Block::BufUsed;

    // Handle wrapping around to the first block.
    if (index_ == static_cast<int32_t>(blockNum_) - 1)
    {
        index_ = 0;
        if (blocks_[index_].status != Block::BufFree)
        {
            std::cerr << "Warning: Block 0 is not free.\n";
            return false;
        }
    }
    else
    {
        // Move to the next block in the sequence.
        index_ += 1;
        if (index_ >= static_cast<int32_t>(blockNum_))
        {
            std::cerr << "Error: Index exceeded block number.\n";
            return false;
        }
        if (blocks_[index_].status != Block::BufFree)
        {
            std::cerr << "Warning: Block " << index_ << " is not free.\n";
            return false;
        }
    }
    return true;
}

解析:

  • 功能:尝试获取下一个空闲的 Block
  • 实现
    • 检查当前 Block 是否为空闲状态。
    • 若不为空闲且不允许重试,标记当前 Block 为已使用,并尝试下一个 Block
    • 添加了回绕逻辑,确保环形缓冲区的循环特性。
    • 在获取失败时,输出警告或错误信息。
checkSpecial 方法
/**
 * @brief Checks for the presence of special characters in the buffer.
 *
 * This method scans the buffer for '#' or '$' characters.
 *
 * @param buf Pointer to the buffer to check.
 * @param length Length of the buffer in bytes.
 * @return The index of the first special character found; -1 if none are found.
 */
int checkSpecial(const uint8_t* buf, uint32_t length) const
{
    for (uint32_t i = 0; i < length; ++i)
    {
        if (buf[i] == '#' || buf[i] == '$')
        {
            return static_cast<int>(i);
        }
    }
    return -1;
}

解析:

  • 功能:检查缓冲区中是否存在特殊字符(#$)。
  • 实现
    • 遍历缓冲区数据,查找特殊字符。
    • 若找到,返回其索引;否则,返回 -1

4. 内存管理与拷贝控制

在本实现中,内存管理拷贝控制 是确保 RingBuffer 类安全高效运行的关键因素。

  1. 原始指针管理内存

    • 每个 Block 使用 new 分配固定大小的缓冲区,并在析构函数中使用 delete[] 释放。
    • 这种手动管理内存的方法需要开发者确保在所有情况下都正确分配和释放资源,避免内存泄漏或重复释放。
  2. 拷贝控制

    • 通过 删除拷贝构造函数和拷贝赋值运算符,防止 BlockRingBuffer 实例被浅拷贝,避免多个实例指向同一内存区域。
    • 移动构造函数和移动赋值运算符 的实现,允许资源的安全转移,提高类的灵活性。

5. 完整代码


/**
 * @brief A ring buffer implementation for managing fixed-size memory blocks without using smart pointers.
 */
class RingBuffer
{
  public:
    /**
     * @brief Represents a single block within the ring buffer.
     */
    struct Block
    {
        uint8_t* buf;     ///< Buffer to store data.
        int8_t   status;  ///< Status of the block (free, using, or used).
        int32_t  index;   ///< Index of the block within the ring buffer.
        uint32_t written; ///< Number of bytes written to the block.

        /**
         * @brief Constructs a Block with a pre-allocated buffer.
         *
         * Initializes the buffer and sets default values for other members.
         */
        Block() : buf(new uint8_t[BlockSize]), status(BufFree), index(-1), written(0)
        {
            // Initialize buffer to zero (optional)
            std::memset(buf, 0, BlockSize);
        }

        /**
         * @brief Destructor to deallocate the buffer.
         */
        ~Block() { delete[] buf; }

        // Delete copy constructor and copy assignment operator to prevent shallow copies.
        Block(const Block&)            = delete;
        Block& operator=(const Block&) = delete;

        // Define move constructor and move assignment operator for safe transfers.
        Block(Block&& other) noexcept : buf(other.buf), status(other.status), index(other.index), written(other.written)
        {
            other.buf     = nullptr;
            other.status  = BufFree;
            other.index   = -1;
            other.written = 0;
        }

        Block& operator=(Block&& other) noexcept
        {
            if (this != &other)
            {
                delete[] buf;

                buf     = other.buf;
                status  = other.status;
                index   = other.index;
                written = other.written;

                other.buf     = nullptr;
                other.status  = BufFree;
                other.index   = -1;
                other.written = 0;
            }
            return *this;
        }

        static constexpr uint32_t BlockSize = 256; ///< Size of each block in bytes.
        static constexpr int8_t   BufFree   = 0;   ///< Indicates the block is free.
        static constexpr int8_t   BufUsing  = 1;   ///< Indicates the block is currently in use.
        static constexpr int8_t   BufUsed   = 2;   ///< Indicates the block has been used.
    };

    /**
     * @brief Constructs a RingBuffer with a specified number of blocks.
     *
     * @param num The number of blocks in the ring buffer. Defaults to 1000.
     */
    explicit RingBuffer(uint32_t num = 1000) : blocks_(num), blockNum_(num), index_(0), indexUsed_(-1)
    {
        // Initialize each block's index.
        for (uint32_t i = 0; i < blocks_.size(); ++i)
        {
            blocks_[i].index = static_cast<int32_t>(i);
        }
        // Set the first block's status to 'using'.
        if (!blocks_.empty())
        {
            blocks_[0].status = Block::BufUsing;
        }
    }

    /**
     * @brief Destructor to deallocate all blocks.
     */
    ~RingBuffer() = default; // Blocks are automatically destroyed, and their destructors handle buffer deallocation.

    // Delete copy constructor and copy assignment operator to prevent shallow copies.
    RingBuffer(const RingBuffer&)            = delete;
    RingBuffer& operator=(const RingBuffer&) = delete;

    // Define move constructor and move assignment operator for safe transfers.
    RingBuffer(RingBuffer&& other) noexcept
        : blocks_(std::move(other.blocks_)),
          blockNum_(other.blockNum_),
          index_(other.index_),
          indexUsed_(other.indexUsed_)
    {
        other.blockNum_  = 0;
        other.index_     = 0;
        other.indexUsed_ = -1;
    }

    RingBuffer& operator=(RingBuffer&& other) noexcept
    {
        if (this != &other)
        {
            blocks_    = std::move(other.blocks_);
            blockNum_  = other.blockNum_;
            index_     = other.index_;
            indexUsed_ = other.indexUsed_;

            other.blockNum_  = 0;
            other.index_     = 0;
            other.indexUsed_ = -1;
        }
        return *this;
    }

    /**
     * @brief Appends data to the ring buffer.
     *
     * This method attempts to append the provided data to the current block.
     * If there's insufficient space or a retry is requested, it tries to acquire the next free block.
     * It also handles splitting data when special characters are encountered.
     *
     * @param buf Pointer to the data to append.
     * @param length Length of the data in bytes.
     * @param retry Flag indicating whether to retry acquiring a free block.
     * @return True if the data was successfully appended; false otherwise.
     */
    bool append(const uint8_t* buf, uint32_t length, bool retry = false)
    {
        // Validate that the data length does not exceed block size.
        if (length > Block::BlockSize)
        {
            std::cerr << "Error: Length " << length << " exceeds block size " << Block::BlockSize << ".\n";
            return false;
        }

        // Check if the current block has enough space and is in the correct status.
        if (!retry && blocks_[index_].written + length <= Block::BlockSize)
        {
            if (blocks_[index_].status != Block::BufUsing)
            {
                std::cerr << "Error: Current block status is not BufUsing.\n";
                return false;
            }
        }
        else
        {
            // Attempt to acquire the next free block.
            if (!nextFree(retry))
            {
                // If acquiring a free block fails, ensure the current block status is correct.
                if (blocks_[index_].status != Block::BufUsed)
                {
                    std::cerr << "Error: Current block status is not BufUsed.\n";
                }
                return false;
            }
            // Update the status of the newly acquired block.
            blocks_[index_].status = Block::BufUsing;
        }

        // If the current block is empty, copy the entire buffer.
        if (blocks_[index_].written == 0)
        {
            std::memcpy(blocks_[index_].buf + blocks_[index_].written, buf, length);
            blocks_[index_].written += length;
        }
        else
        {
            // Check for special characters in the buffer.
            int specialIdx = checkSpecial(buf, length);
            if (specialIdx == -1)
            {
                // No special characters found; attempt to copy the entire buffer.
                if (blocks_[index_].written + length > Block::BlockSize)
                {
                    std::cerr << "Error: Not enough space to append data without special character.\n";
                    return false;
                }
                std::memcpy(blocks_[index_].buf + blocks_[index_].written, buf, length);
                blocks_[index_].written += length;
            }
            else
            {
                // Special character found; split the data at the special character.
                if (blocks_[index_].written + specialIdx > Block::BlockSize)
                {
                    std::cerr << "Error: Not enough space to copy data before special character.\n";
                    return false;
                }
                // Copy data up to the special character.
                std::memcpy(blocks_[index_].buf + blocks_[index_].written, buf, specialIdx);
                blocks_[index_].written += specialIdx;

                // Attempt to acquire a new free block for the special character.
                if (!nextFree(retry))
                {
                    if (blocks_[index_].status != Block::BufUsed)
                    {
                        std::cerr << "Error: Current block status is not BufUsed after finding special character.\n";
                    }
                    return false;
                }

                // Update the status and reset the write position for the new block.
                blocks_[index_].status  = Block::BufUsing;
                blocks_[index_].written = 0;

                // Copy the special character to the new block.
                blocks_[index_].buf[blocks_[index_].written++] = buf[specialIdx];

                // If there is remaining data after the special character, copy it to the new block.
                if (static_cast<uint32_t>(specialIdx + 1) < length)
                {
                    uint32_t remainingLength = length - specialIdx - 1;
                    if (remainingLength > Block::BlockSize - blocks_[index_].written)
                    {
                        std::cerr << "Error: Not enough space to copy remaining data after special character.\n";
                        return false;
                    }
                    std::memcpy(blocks_[index_].buf + blocks_[index_].written, buf + specialIdx + 1, remainingLength);
                    blocks_[index_].written += remainingLength;
                }
            }
        }
        return true;
    }

    /**
     * @brief Retrieves the currently used block.
     *
     * @return A constant reference to the currently used block.
     * @throws std::out_of_range if the indexUsed_ is invalid.
     */
    const Block& currentUsed() const
    {
        if (indexUsed_ >= 0 && static_cast<size_t>(indexUsed_) < blocks_.size())
        {
            return blocks_[indexUsed_];
        }
        throw std::out_of_range("currentUsed(): indexUsed_ is out of range.");
    }

    /**
     * @brief Retrieves the block that is currently being used for writing.
     *
     * @return A constant reference to the block currently in use.
     * @throws std::out_of_range if the index_ is invalid.
     */
    const Block& currentUsing() const
    {
        if (static_cast<size_t>(index_) < blocks_.size())
        {
            return blocks_[index_];
        }
        throw std::out_of_range("currentUsing(): index_ is out of range.");
    }

    /**
     * @brief Releases a block, marking it as free and resetting its written bytes.
     *
     * @param idx The index of the block to release.
     */
    void release(uint32_t idx)
    {
        if (idx < blocks_.size())
        {
            blocks_[idx].written = 0;
            blocks_[idx].status  = Block::BufFree;
        }
        else
        {
            std::cerr << "Error: Release index " << idx << " is out of bounds.\n";
        }
    }

    /**
     * @brief Retrieves the next used block's index.
     *
     * @return The index of the next used block, or -1 if no such block exists.
     */
    int32_t nextUsed()
    {
        // Check if the current used index is valid and marked as used.
        if (indexUsed_ >= 0 && static_cast<size_t>(indexUsed_) < blocks_.size() &&
            blocks_[indexUsed_].status == Block::BufUsed)
        {
            return indexUsed_;
        }

        // Handle wrapping around to the beginning of the ring buffer.
        if (indexUsed_ == static_cast<int32_t>(blockNum_) - 1)
        {
            if (!blocks_.empty() && blocks_[0].status == Block::BufUsed)
            {
                indexUsed_ = 0;
                return indexUsed_;
            }
            else
            {
                return -1;
            }
        }
        else
        {
            // Check the next block in sequence.
            if ((indexUsed_ + 1) < static_cast<int32_t>(blockNum_) && blocks_[indexUsed_ + 1].status == Block::BufUsed)
            {
                indexUsed_ += 1;
                return indexUsed_;
            }
            else
            {
                return -1;
            }
        }
    }

  private:
    /**
     * @brief Attempts to acquire the next free block in the ring buffer.
     *
     * @param retry Flag indicating whether to retry acquiring a free block.
     * @return True if a free block was successfully acquired; false otherwise.
     */
    bool nextFree(bool retry = false)
    {
        // If the current block is free, it can be used.
        if (blocks_[index_].status == Block::BufFree)
        {
            return true;
        }
        else
        {
            // If retry is requested and the block is not free, do not attempt further.
            if (retry)
            {
                return false;
            }
        }

        // Mark the current block as used since it cannot be reused immediately.
        blocks_[index_].status = Block::BufUsed;

        // Handle wrapping around to the first block.
        if (index_ == static_cast<int32_t>(blockNum_) - 1)
        {
            index_ = 0;
            if (blocks_[index_].status != Block::BufFree)
            {
                std::cerr << "Warning: Block 0 is not free.\n";
                return false;
            }
        }
        else
        {
            // Move to the next block in the sequence.
            index_ += 1;
            if (index_ >= static_cast<int32_t>(blockNum_))
            {
                std::cerr << "Error: Index exceeded block number.\n";
                return false;
            }
            if (blocks_[index_].status != Block::BufFree)
            {
                std::cerr << "Warning: Block " << index_ << " is not free.\n";
                return false;
            }
        }
        return true;
    }

    /**
     * @brief Checks for the presence of special characters in the buffer.
     *
     * This method scans the buffer for '#' or '$' characters.
     *
     * @param buf Pointer to the buffer to check.
     * @param length Length of the buffer in bytes.
     * @return The index of the first special character found; -1 if none are found.
     */
    int checkSpecial(const uint8_t* buf, uint32_t length) const
    {
        for (uint32_t i = 0; i < length; ++i)
        {
            if (buf[i] == '#' || buf[i] == '$')
            {
                return static_cast<int>(i);
            }
        }
        return -1;
    }

    std::vector<Block> blocks_;         ///< Vector of blocks managed by the ring buffer.
    int32_t            index_     = 0;  ///< Current index for writing data.
    int32_t            blockNum_  = 0;  ///< Total number of blocks in the ring buffer.
    int32_t            indexUsed_ = -1; ///< Index of the currently used block.
};

;