6
\$\begingroup\$

As you may know, C++20 has added std::atomic<std::shared_ptr<T>> specialization to the standard, but sadly, most compilers have not implemented it yet. So I decided to implement it myself.

I want to know if I can improve this code or not. In addition, I'm not sure if my implementations of wait(), notify_one() and notify_all() are correct using condition variable.

#include <atomic>
#include <memory>
#include <condition_variable>
#include <thread>
#include <version>
#if !defined(__cpp_lib_atomic_shared_ptr) || (__cpp_lib_atomic_shared_ptr == 0)
namespace std {
template<typename T> 
struct atomic<shared_ptr<T>> {
 using value_type = shared_ptr<T>;
 static constexpr bool is_always_lock_free = false;
 bool is_lock_free() const noexcept {
 return false;
 }
 constexpr atomic() noexcept {};
 atomic(shared_ptr<T> desired) noexcept : ptr_(std::move(desired)) {};
 atomic(const atomic&) = delete;
 void operator=(const atomic&) = delete;
 void store(shared_ptr<T> desired, memory_order order = memory_order::seq_cst) noexcept {
 std::lock_guard<std::mutex> lk(cv_m_);
 atomic_store_explicit(&ptr_, std::move(desired), order);
 }
 void operator=(shared_ptr<T> desired) noexcept {
 store(std::move(desired));
 }
 shared_ptr<T> load(memory_order order = memory_order::seq_cst) const noexcept {
 return atomic_load_explicit(&ptr_, order);
 }
 operator shared_ptr<T>() const noexcept {
 return load();
 }
 shared_ptr<T> exchange(shared_ptr<T> desired, memory_order order = memory_order::seq_cst) noexcept {
 return atomic_exchange_explicit(&ptr_, std::move(desired), order);
 }
 bool compare_exchange_weak(shared_ptr<T>& expected, shared_ptr<T> desired,
 memory_order success, memory_order failure) noexcept {
 return atomic_compare_exchange_weak_explicit(&ptr_, &expected, std::move(desired), success, failure);
 }
 bool compare_exchange_strong(shared_ptr<T>& expected, shared_ptr<T> desired,
 memory_order success, memory_order failure) noexcept {
 return atomic_compare_exchange_strong_explicit(&ptr_, &expected, std::move(desired), success, failure);
 }
 bool compare_exchange_weak(shared_ptr<T>& expected, shared_ptr<T> desired,
 memory_order order = memory_order::seq_cst) noexcept {
 return compare_exchange_weak(expected, std::move(desired), order, convert_order(order));
 }
 bool compare_exchange_strong(shared_ptr<T>& expected, shared_ptr<T> desired,
 memory_order order = memory_order::seq_cst) noexcept {
 return compare_exchange_strong(expected, std::move(desired), order, convert_order(order));
 }
 void wait(shared_ptr<T> old, memory_order order = memory_order::seq_cst) const noexcept {
 std::unique_lock<std::mutex> lk(cv_m_);
 cv_.wait(lk, [&]{ return !(load(order) == old); });
 }
 void notify_one() noexcept {
 cv_.notify_one();
 }
 void notify_all() noexcept {
 cv_.notify_all();
 }
private:
 shared_ptr<T> ptr_;
 mutable std::condition_variable cv_;
 mutable std::mutex cv_m_;
 constexpr memory_order convert_order(memory_order order) {
 switch(order) {
 case std::memory_order_acq_rel:
 return std::memory_order_acquire;
 
 case std::memory_order_release:
 return std::memory_order_relaxed;
 default:
 return order;
 }
 }
};
}
#endif
int main() {
 std::atomic<std::shared_ptr<int>> a;
}

I will be happy about your suggestions. Thanks in advance.

Toby Speight
87.9k14 gold badges104 silver badges325 bronze badges
asked May 12, 2021 at 15:07
\$\endgroup\$

1 Answer 1

2
\$\begingroup\$

Simple things:

  • operator= (削除) should (削除ここまで) would normally return *this rather than void:

    atomic& operator=(value_type desired) noexcept {
     store(std::move(desired));
     return *this;
    }
    

    But it seems that the standard actually requires void return here, so the implementation is correct.

  • Default construction should initialize members:

    private:
     value_type ptr_= {};
     mutable std::condition_variable cv_ = {};
     mutable std::mutex cv_m_= {};
    
  • Since we're writing for C++20, we can take advantage of class template argument deduction to simplify, e.g.

     std::lock_guard lk{cv_m_};
    

I don't like the name of convert_order(). I might call it something like order_without_release(), to be more specific about why it's used. Yes, it's a private function, but it's still important to keep it clear.

Similarly, I'd name the success and failure arguments more explicitly - perhaps success_mem_order etc.?

I think that convert_order ought to be constexpr static, since it doesn't use *this.


Our implementation is not lock-free:

static constexpr bool is_always_lock_free = false;
bool is_lock_free() const noexcept {
 return false;
}

A library implementation with access to internals of shared-pointer might be able to avoid that, but we can't. Still, it's better than no implementation at all.


Otherwise, this looks pretty good. We should be able to use a very similar implementation to provide std::shared_ptr<std::weak_ptr<T>>, too.

answered Jan 11, 2022 at 11:13
\$\endgroup\$
5
  • \$\begingroup\$ I really don't know why I have messed up first 2 points.... <.< \$\endgroup\$ Commented Jan 11, 2022 at 11:24
  • \$\begingroup\$ I just remembered why my operator= returns void. because I copied template of this class from standard: eel.is/c++draft/util.smartptr.atomic.shared in addition, in cpp ref it also returns void. en.cppreference.com/w/cpp/memory/shared_ptr/atomic2 \$\endgroup\$ Commented Jan 11, 2022 at 11:32
  • \$\begingroup\$ Ah, interesting. And the non-specialised std::atomic returns a copy of the stored value. I'm not sure why. \$\endgroup\$ Commented Jan 11, 2022 at 11:43
  • 1
    \$\begingroup\$ @TobySpeight, my expectation would be that the value returned from = is exactly the same used for assignment itself. Since atomics can change between any atomic operations, I guess returning a copy would lead to less bugs that are extremely hard to reproduce. \$\endgroup\$ Commented Jan 11, 2022 at 12:45
  • 1
    \$\begingroup\$ I think because if operator= for this specialization returns a copy, internal shared_ptr's reference count should be increased which is not logical. Then they probably decided to use void returning function. \$\endgroup\$ Commented Jan 11, 2022 at 12:58

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.