Utoljára aktív 1784651520

Revízió dc0ef7e3683caf6fac9cecde49516f47820193c2

gistfile2.md Eredeti

💬 Chat Export: Create a C++ triple buffer cla...

Exported on: 7/21/2026, 9:28:41 AM



🧑 User
7/21/2026, 9:28:41 AM

Create a C++ triple buffer class which safely moves data between two threads using atomics. Template this class on the data type to be buffered between the two threads. This data structure should have the same semantics as a "double buffer" common in graphics, where the swapped buffer always contains the latest data. This should not behave like a queue with 3 slots.


🤖 Assistant
7/21/2026, 9:28:41 AM

Based on the provided context, here is a C++ triple buffer class template that safely moves data between two threads using atomics, following the semantics of a double buffer where the swapped buffer always contains the latest data.

#include <atomic>
#include <type_traits>

template<typename T>
class TripleBuffer {
public:
    // Constructor with initial value
    explicit TripleBuffer(const T& init) noexcept(std::is_nothrow_copy_constructible_v<T>)
        : m_buffers{slot{init}, slot{init}, slot{init}} {
    }

    // Default constructor (requires default constructible type)
    TripleBuffer() noexcept(std::is_nothrow_default_constructible_v<T>)
        requires std::is_default_constructible_v<T> = default;

    // Producer method: write new data to the current write buffer
    void produce(T value) noexcept {
        m_buffers[m_write_idx].data = value;
        
        const uint8_t new_mid = static_cast<uint8_t>(m_write_idx | dirty_bit);
        const uint8_t old_mid = m_mid_state.exchange(new_mid, std::memory_order_acq_rel);
        m_write_idx = old_mid & index_mask;
    }

    // Consumer method: read the latest available data if available
    bool consume(T& result) noexcept {
        const uint8_t state = m_mid_state.load(std::memory_order_acquire);
        if (!(state & dirty_bit)) {
            return false; // No new data available
        }

        const uint8_t new_mid = m_read_idx;
        const uint8_t old_mid = m_mid_state.exchange(new_mid, std::memory_order_acq_rel);
        m_read_idx = old_mid & index_mask;

        // Swap the result with the data in the read buffer
        swap(result, m_buffers[m_read_idx].data);
        return true;
    }

    // Return a const reference to the consumer's current slot (only for copyable types)
    const T& read_buffer() const noexcept 
        requires std::is_copy_assignable_v<T> {
        return m_buffers[m_read_idx].data;
    }

    // Check if there is new data available
    bool has_new_data() const noexcept {
        return m_mid_state.load(std::memory_order_acquire) & dirty_bit;
    }

private:
    static constexpr uint8_t index_mask = 0x03;
    static constexpr uint8_t dirty_bit = 0x04;

    struct alignas(64) slot {
        T data{};
        slot() = default;
        template<typename U>
        explicit slot(U&& val) : data(std::forward<U>(val)) {}
    };

    std::array<slot, 3> m_buffers{};
    alignas(64) std::atomic<uint8_t> m_mid_state{1};
    alignas(64) uint8_t m_write_idx{0};
    alignas(64) uint8_t m_read_idx{2};

    // Helper function to swap two values
    template<typename U>
    void swap(U& a, U& b) noexcept {
        using std::swap;
        swap(a, b);
    }
};

This implementation follows the design principles outlined in the provided context, particularly focusing on the use of atomic operations to ensure thread safety and the correct ordering of memory operations. The produce method allows the producer thread to write new data to the current write buffer, while the consume method enables the consumer thread to read the latest available data. The read_buffer method provides a way to access the current read buffer, and has_new_data allows checking if new data is available.

The implementation uses three buffers: one for writing, one for reading, and one as a temporary buffer. The atomic variable m_mid_state is used to coordinate between the producer and consumer threads, ensuring that the correct buffer is selected for reading or writing. This approach ensures that the data structure behaves like a double buffer, where the swapped buffer always contains the latest data, without behaving like a queue with three slots.

Note that this implementation assumes that the data type T is copyable and that the necessary memory ordering is used to ensure correctness. The implementation also includes error handling for cases where no new data is available, preventing the consumer from accessing stale or incomplete data.

This implementation is designed to be efficient and safe for use in multi-threaded applications, leveraging the power of atomic operations to provide lock-free concurrency.

Citations: