summaryrefslogtreecommitdiff
path: root/src/utils/cpp/atomic.hpp
blob: 7f7631d014990a4c18960fe3bc7df39acbe263de (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
#ifndef INCLUDED_SRC_UTILS_CPP_ATOMIC_HPP
#define INCLUDED_SRC_UTILS_CPP_ATOMIC_HPP

#include <atomic>
#include <condition_variable>
#include <shared_mutex>

// Atomic wrapper with notify/wait capabilities.
// TODO(modernize): Replace any use this class by C++20's std::atomic<T>, once
// libcxx adds support for notify_*() and wait().
// [https://libcxx.llvm.org/docs/Cxx2aStatus.html]
template <class T>
class atomic {
  public:
    atomic() = default;
    explicit atomic(T value) : value_{std::move(value)} {}
    atomic(atomic const& other) = delete;
    atomic(atomic&& other) = delete;
    ~atomic() = default;

    auto operator=(atomic const& other) -> atomic& = delete;
    auto operator=(atomic&& other) -> atomic& = delete;
    auto operator=(T desired) -> T {  // NOLINT
        std::shared_lock lock(mutex_);
        value_ = desired;
        return desired;
    }
    operator T() const { return static_cast<T>(value_); }  // NOLINT

    void store(T desired, std::memory_order order = std::memory_order_seq_cst) {
        std::shared_lock lock(mutex_);
        value_.store(std::move(desired), order);
    }
    [[nodiscard]] auto load(
        std::memory_order order = std::memory_order_seq_cst) const -> T {
        return value_.load(order);
    }

    template <class U = T, class = std::enable_if_t<std::is_integral_v<U>>>
    auto operator++() -> T {
        std::shared_lock lock(mutex_);
        return ++value_;
    }
    template <class U = T, class = std::enable_if_t<std::is_integral_v<U>>>
    [[nodiscard]] auto operator++(int) -> T {
        std::shared_lock lock(mutex_);
        return value_++;
    }
    template <class U = T, class = std::enable_if_t<std::is_integral_v<U>>>
    auto operator--() -> T {
        std::shared_lock lock(mutex_);
        return --value_;
    }
    template <class U = T, class = std::enable_if_t<std::is_integral_v<U>>>
    [[nodiscard]] auto operator--(int) -> T {
        std::shared_lock lock(mutex_);
        return value_--;
    }

    void notify_one() { cv_.notify_one(); }
    void notify_all() { cv_.notify_all(); }
    void wait(T old,
              std::memory_order order = std::memory_order::seq_cst) const {
        std::unique_lock lock(mutex_);
        cv_.wait(lock,
                 [this, &old, order]() { return value_.load(order) != old; });
    }

  private:
    std::atomic<T> value_{};
    mutable std::shared_mutex mutex_{};
    mutable std::condition_variable_any cv_{};
};

// Atomic shared_pointer with notify/wait capabilities.
// TODO(modernize): Replace any use this class by C++20's
// std::atomic<std::shared_ptr<T>>, once libcxx adds support for it.
// [https://libcxx.llvm.org/docs/Cxx2aStatus.html]
template <class T>
class atomic_shared_ptr {
    using ptr_t = std::shared_ptr<T>;

  public:
    atomic_shared_ptr() = default;
    explicit atomic_shared_ptr(ptr_t value) : value_{std::move(value)} {}
    atomic_shared_ptr(atomic_shared_ptr const& other) = delete;
    atomic_shared_ptr(atomic_shared_ptr&& other) = delete;
    ~atomic_shared_ptr() = default;

    auto operator=(atomic_shared_ptr const& other)
        -> atomic_shared_ptr& = delete;
    auto operator=(atomic_shared_ptr&& other) -> atomic_shared_ptr& = delete;
    auto operator=(ptr_t desired) -> ptr_t {  // NOLINT
        std::shared_lock lock(mutex_);
        value_ = desired;
        return desired;
    }
    operator ptr_t() const { value_; }  // NOLINT

    void store(ptr_t desired) {
        std::shared_lock lock(mutex_);
        value_ = std::move(desired);
    }
    [[nodiscard]] auto load() const -> ptr_t { return value_; }

    void notify_one() { cv_.notify_one(); }
    void notify_all() { cv_.notify_all(); }
    void wait(ptr_t old) const {
        std::unique_lock lock(mutex_);
        cv_.wait(lock, [this, &old]() { return value_ != old; });
    }

  private:
    ptr_t value_{};
    mutable std::shared_mutex mutex_{};
    mutable std::condition_variable_any cv_{};
};

#endif  // INCLUDED_SRC_UTILS_CPP_ATOMIC_HPP