Context: I have a queue that supports single-read/single-write from two different(/or not) threads, to enforce this behaviour i.e. single-reader/single-writer at a time I need to limit the number of the threads owning the queue at a time to 2 (the writer already owns the queue), I was thinking then of creating a shared_ptr to the queue with a max ref count known at compile time set to 2. Hence my question is the below.
Question: Is there a way to implement a shared pointer (maybe using unique_pointer's) with a maximum ref count that is known at compile time? My case is max_ref_count = 2 i.e. exceeding ref count limit = 2 should be a compile-time error.
const auto p = std::make_shared<2, double>(2159); //works fine
const auto q = p; // works fine
const auto err1 = p; // does not compile
const auto err2 = q; // does not compile
Impossible at compile-time
What you're doing isn't possible at compile-time, only at runtime. Note that
std::shared_ptris copyable, so it's possible that "copy paths diverge":At this point,
BandCdon't know anything about each other, and there is no way to ensure that they do through the type system. You can at best count the length of the copy path fromAtoBand subsequent copies, but not the global amount of copies.This would require some form of stateful metaprogramming and C++ does not support that.
Difficult at run-time
Note two problems:
std::shared_ptris also expected to have thread-safe counting. This also means thatstd::shared_ptr::use_count()may not yield the most recent result and cannot be considered a reliable metric.intin addition to the atomic counter that shared pointers already have. This is somewhat annoying, but doable.Single-threaded solution
If you don't consider the problems of multi-threading, you can just use
std::shared_ptr::use_count()to keep track of uses. This is a reliable metric in a single-threaded program. You just need to make a wrapper forstd::shared_ptrwhich throws whenever the limited is exceeded, which could happen in the copy constructor and the copy assignment operator.Multi-threaded solution
This is a little bit more complicating, and I will only provide the general outline.
You can create a
std::shared_ptrwith a custom deleter. This custom deleter can contain astd::atomic<std::size_t>to keep reliable track of the use count.With this, you don't need to manage resources yourself, but can let
std::shared_ptrdo it and access the deleter withbase.get_deleter<counting_deleter>()any time.In the copy constructor, similar to the single-threaded solution, you would check:
Basically, we check if increasing the use count is possible. If not, we throw, otherwise we attempt to increment the
use_countwithcompare_exchange_weak.The copy assignment operator is analogous. The other special member functions can be defaulted, since moving or destroying cannot blow the limit.