C++ Logo

std-proposals

Advanced search

Re: [std-proposals] Suggestion: non-static member variables for static-duration-only classes

From: Frederick Virchanza Gotham <cauldwell.thomas_at_[hidden]>
Date: Sun, 5 Oct 2025 13:26:07 +0100
> Actually just use the lockfree queue from Boost. I did this on my phone so
I can't compile it, so maybe it doesn't compile successfully, but anyway:

#include <cassert>
#include <array>
#include <atomic>
#include <optional>
#include <set>
#include <stdexcept>
#include <thread>
#include <utility>
#include <vector>
#include <mutex>

#include <boost/lockfree/queue.hpp>

template<typename T, unsigned max_threads>
class ThreadLocal final {
    static inline std::array<std::optional<T>, max_threads> storage{};
    static inline std::atomic<unsigned> nextIndex{0u};
    static inline boost::lockfree::queue<unsigned> freeList{max_threads};

    static unsigned acquireSlot(void) noexcept(false)
    {
        unsigned index;
        if ( freeList.pop(index) ) return index;

        index = nextIndex.fetch_add(1u, std::memory_order_relaxed);
        if ( index >= max_threads )
            throw std::runtime_error("Exceeded max_threads limit!");
        return index;
    }

    static void releaseSlot(unsigned const index) noexcept
    {
        storage[index].reset();
        freeList.push(index);
    }

    template<typename... Params>
    unsigned registerThread(Params&&... args) const noexcept(false)
    {
        static thread_local unsigned const myIndex = acquireSlot();

        struct Reclaimer {
            unsigned idx;
            ~Reclaimer(void) noexcept { releaseSlot(idx); }
        };

        static thread_local Reclaimer const reclaimer{myIndex};

        if ( false == storage[myIndex].has_value() )
            storage[myIndex].emplace( std::forward<Params>(args)... );
        return myIndex;
    }

public:
    template<typename... Params>
    T &operator()(Params&&... args) noexcept(false)
    {
        static thread_local unsigned const slot =
            registerThread(std::forward<Params>(args)...);
        return *storage[slot];
    }

    template<typename... Params>
    T const &operator()(Params&&... args) const noexcept(false)
    {
        return const_cast<ThreadLocal*>(this)
            ->operator()( std::forward<Params>(args)... );
    }
};

// ===================
// Example Usage
// ===================

class Counter {
    std::atomic<unsigned> total{0u};
    std::atomic<unsigned> num_threads{0u};
    std::mutex mtx;

    struct PerThread {
        std::atomic<unsigned> count{0u};
        Counter &parent;

        explicit PerThread(Counter *const c) noexcept(false) : parent(*c)
        {
            std::lock_guard lock{parent.mtx};
            parent.pt_active.insert(this);
            parent.num_threads.fetch_add(1u, std::memory_order_relaxed);
        }

        ~PerThread(void) noexcept // we want std::terminate called
        {
            {
                std::lock_guard lock{parent.mtx};
                parent.pt_active.erase(this);
            }

            if ( unsigned const leftover =
                    count.load(std::memory_order_relaxed) )
                parent.total.fetch_add(leftover, std::memory_order_relaxed);

            parent.num_threads.fetch_sub(1u, std::memory_order_relaxed);
        }
    };

    std::set<PerThread*> pt_active;
    ThreadLocal<PerThread, 32u> pt;

public:
    void incr(void) noexcept
    {
        pt(this).count.fetch_add(1u, std::memory_order_relaxed);
    }

    unsigned collect(void) noexcept(false)
    {
        unsigned sum = 0u;

        {
            std::lock_guard lock{mtx};
            for ( PerThread *const p : pt_active )
                sum += p->count.exchange(0u, std::memory_order_relaxed);
        }

        if ( sum ) total.fetch_add(sum, std::memory_order_relaxed);
        return sum;
    }
};

#include <iostream>

auto main(void) -> int
{
    Counter counter;

    constexpr unsigned numThreads = 6u;
    constexpr unsigned iterations = 200u;

    constexpr auto work = [&](unsigned)
    {
        for ( unsigned i = 0; i < iterations; ++i ) counter.incr();
    };

    std::vector<std::thread> threads;
    threads.reserve(numThreads);

    for ( unsigned i = 0; i < numThreads; ++i ) threads.emplace_back(work,
i);

    for (auto &t : threads) t.join();

    std::cout << "Collected total = " << counter.collect() << std::endl;
}

Received on 2025-10-05 12:26:12