Naposledy aktivní 1784651385

gistfile1.md Raw

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

Exported on: 7/21/2026, 7:27:33 AM



🧑 User
7/21/2026, 7:27:33 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, 7:27:33 AM

Based on the provided context, here is a C++ triple buffer class template designed for safe data movement between two threads using atomics. This implementation follows the "double buffer" semantics common in graphics, where one buffer is always presented (front), another is being written to (back), and a third acts as an intermediate (middle) buffer for swapping and signaling new data availability.

#include <atomic>

template<typename T>
class TripleBuffer {
public:
    // Consumer side: reads from the buffer
    T& Read() {
        // Check if there's newly committed data in the middle buffer
        bool isDirty = dirty_.load(std::memory_order_relaxed);
        if (isDirty) {
            // Swap the front buffer with the middle buffer
            front_buffer_ = middle_buffer_.exchange(front_buffer_, std::memory_order_acq_rel);
            // Reset the dirty flag
            dirty_.store(false, std::memory_order_relaxed);
        }
        return *front_buffer_;
    }

    // Producer side: gets the current back buffer to write to it
    T& Write() {
        return *back_buffer_;
    }

    // Producer side: commits written data and swaps the back buffer with the middle buffer
    void Commit() {
        // Swap buffer pointers and mark the middle buffer dirty
        back_buffer_ = middle_buffer_.exchange(back_buffer_, std::memory_order_acq_rel);
        dirty_.store(true, std::memory_order_relaxed);
    }

private:
    // Prevent implicit data sharing between threads
    static constexpr size_t kNoSharing = 64;
    struct alignas(kNoSharing) Buffer {
        T data{};
    };
    Buffer buffers_[3];

    // Pointers to the three buffers
    alignas(kNoSharing) T* front_buffer_{&buffers_[0].data};   // Only consumer accesses this
    alignas(kNoSharing) T* back_buffer_{&buffers_[2].data};    // Only producer accesses this
    alignas(kNoSharing) T* middle_buffer_{&buffers_[1].data};  // Used for swapping

    // Dirty flag: indicates that there's new data in the middle buffer
    std::atomic<bool> dirty_{false};
};

// Example usage:
// TripleBuffer<int> triple_buffer;
//
// // Producer thread
// while (true) {
//     int& buffer = triple_buffer.Write();
//     buffer = 42; // Write your data
//     triple_buffer.Commit(); // Signal that data is ready
// }
//
// // Consumer thread
// while (true) {
//     int& buffer = triple_buffer.Read();
//     // Use the data
//     std::cout << buffer << std::endl;
// }

This implementation ensures that the consumer always sees the latest data available, similar to double buffering in graphics. The Read() method checks the dirty_ flag to determine if a swap is needed, and the Commit() method signals that new data is available by updating the middle_buffer_ pointer and setting the dirty_ flag. The Write() method simply returns a reference to the current back_buffer_ for the producer to write to.

The class uses atomic operations to ensure thread safety, specifically std::memory_order_relaxed, std::memory_order_acquire, and std::memory_order_acq_rel for different operations. The alignas(kNoSharing) directive is used to prevent false sharing between the buffers, which can improve performance on multi-core systems.

Please note that this implementation assumes that the data type T is small enough to fit within a single cache line, which is typically true for most primitive types and small structs. If you are working with larger data types, you may need to adjust the implementation accordingly.

Citations: