Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Copy on Write with shared_ptr

So I have a simple cow_ptr. It looks something like this:

template<class T, class Base=std::shared_ptr<T const>>
struct cow_ptr:private Base{
  using Base::operator*;
  using Base::operator->;
  using Base::operator bool;
  // etc

  cow_ptr(std::shared_ptr<T> ptr):Base(ptr){}

  // defaulted special member functions

  template<class F>
  decltype(auto) write(F&& f){
    if (!unique()) self_clone();
    Assert(unique());
    return std::forward<F>(f)(const_cast<T&>(**this));
  }
private:
  void self_clone(){
    if (!*this) return;
    *this = std::make_shared<T>(**this);
    Assert(unique());
  }
};

this guarantees that it holds a non-const T and ensures it is unique when it .write([&](T&){})s to it.

The c++17 deprecation of .unique() seems to indicate this design is flawed.

I am guessing that if we start with a cow_ptr<int> ptr with 1 in thread A, pass it to thread B, make it unique, modify it to 2, pass ptr it back and read it in thread A we have generated a race condition.

How do I fix this? Can I simply add a memory barrier in write? Which one? Or is the problem more fundamental?

Are symptoms less likely on x86 due to the x86 memory consistency going above and beyond what C++ demands?

like image 609
Yakk - Adam Nevraumont Avatar asked Oct 12 '17 02:10

Yakk - Adam Nevraumont


People also ask

Can shared_ptr be copied?

The ownership of an object can only be shared with another shared_ptr by copy constructing or copy assigning its value to another shared_ptr . Constructing a new shared_ptr using the raw underlying pointer owned by another shared_ptr leads to undefined behavior.

How do you implement a copy on write?

To implement copy-on-write, a smart pointer to the real content is used to encapsulate the object's value, and on each modification an object reference count is checked; if the object is referenced more than once, a copy of the content is created before modification.

Is shared_ptr copy thread-safe?

std::shared_ptr is not thread safe. A shared pointer is a pair of two pointers, one to the object and one to a control block (holding the ref counter, links to weak pointers ...).

Should I use unique_ptr or shared_ptr?

Use unique_ptr when you want to have single ownership(Exclusive) of the resource. Only one unique_ptr can point to one resource. Since there can be one unique_ptr for single resource its not possible to copy one unique_ptr to another. A shared_ptr is a container for raw pointers.


1 Answers

The problem is that use_count() may grow, even if you don't change shared_ptr in this thread by other threads activity.

You may not touch the current shared_ptr by other thread, but other instances of shared_ptr or weak_ptr may be copied/deleted. The use_count() == 1 seem to exclude concurrent shared_ptr, but concurrent weak_ptr are still possible, and may be .lock() ed.

Ok, for your case, you hidden part of shared_ptr interface, so you may have hidden weak_ptr creation ability, so your case may be fine. Too bad that while this question waited for answer, .unique() is removed in C++20. You still have .use_count() though.

Or maybe just implement your own reference counting or take a more suitable base. Remember extra overhead for shared_ptr, most notably, two atomic counters (use and weak) instead of one.

like image 107
Alex Guteniev Avatar answered Oct 07 '22 09:10

Alex Guteniev