Skip to content

Commit d749f73

Browse files
[Util] Close #190.
- Implement `unsharable_shared_ptr` as requested in #190. - Provide factory function `make_unsharable_shared` in the spirit of `std::make_shared`. - Add respective unit test.
1 parent 241684c commit d749f73

File tree

3 files changed

+136
-0
lines changed

3 files changed

+136
-0
lines changed
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
#pragma once
2+
3+
#include <memory>
4+
#include <mutable/util/exception.hpp>
5+
#include <mutable/util/macro.hpp>
6+
7+
8+
namespace m {
9+
10+
/**
11+
* This class extends `std::shared_ptr` to allow for *unsharing* an *exclusively* held object and thereby converting to
12+
* a `std::unique_ptr`.
13+
*
14+
* Beware, that this class relies on accurate
15+
* [`use_count()`](https://en.cppreference.com/w/cpp/memory/shared_ptr/use_count). The `use_count()` is inaccurate if
16+
* the shared_ptr is shared across threads. Therefore, this class must not be used in a shared environment.
17+
*
18+
* If support for multi-threading is needed, a *wrapper* around `unsharable_shared_ptr` may be used together with a
19+
* mutex to guard operations and synchronize on the `use_count()`.
20+
*/
21+
template<typename T>
22+
struct unsharable_shared_ptr : public std::shared_ptr<T>
23+
{
24+
using super = std::shared_ptr<T>;
25+
26+
private:
27+
using deleter_func_type = std::reference_wrapper<void(T*)>;
28+
static void default_deleter(T *ptr) { delete ptr; }
29+
static void noop_deleter(T*) { }
30+
31+
public:
32+
template<typename Y>
33+
explicit unsharable_shared_ptr(Y *ptr) : super(ptr, std::ref(unsharable_shared_ptr::default_deleter)) { }
34+
35+
unsharable_shared_ptr() : super(nullptr) { }
36+
explicit unsharable_shared_ptr(std::nullptr_t) : super(nullptr) { }
37+
38+
unsharable_shared_ptr(const unsharable_shared_ptr&) = default;
39+
unsharable_shared_ptr(unsharable_shared_ptr&&) = default;
40+
41+
/**
42+
* Converts (and thereby moves) the exclusively held object from this `unsharable_shared_ptr` to a
43+
* `std::unique_ptr`.
44+
*
45+
* \return a `std::unique_ptr` owning the referenced object
46+
* \throw `std::logic_error` if this `unsharable_shared_ptr` does not hold the referenced object *exclusively*
47+
*/
48+
std::unique_ptr<T> exclusive_shared_to_unique() {
49+
if (super::use_count() == 0) return nullptr; // nothing to unshare
50+
if (super::use_count() > 1) throw m::invalid_state{"not exclusive"};
51+
*std::get_deleter<deleter_func_type>(*static_cast<super*>(this)) = std::ref(unsharable_shared_ptr::noop_deleter);
52+
std::unique_ptr<T> uptr{super::get()};
53+
super::reset(); // this will *not* delete the referenced object
54+
M_insist(super::get() == nullptr);
55+
return uptr;
56+
}
57+
};
58+
59+
template<typename T, typename... Args>
60+
unsharable_shared_ptr<T> make_unsharable_shared(Args&&... args)
61+
{
62+
return unsharable_shared_ptr<T>{new T(std::forward<Args>(args)...)};
63+
}
64+
65+
}

unittest/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ set(
2020
util/PositionTest.cpp
2121
util/SpnTest.cpp
2222
util/TimerTest.cpp
23+
util/unsharable_shared_ptr_test.cpp
2324

2425
# util/container
2526
util/container/RefCountingHashMapTest.cpp
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
#include "catch2/catch.hpp"
2+
3+
#include <mutable/util/unsharable_shared_ptr.hpp>
4+
#include <string>
5+
6+
7+
using namespace m;
8+
9+
10+
TEST_CASE("unsharable_shared_ptr", "[core][util]")
11+
{
12+
unsharable_shared_ptr<int> sp1(new int(42));
13+
CHECK(sp1.use_count() == 1);
14+
15+
{
16+
auto sp2 = sp1; // copy shared ptr
17+
CHECK(sp1.use_count() == 2);
18+
CHECK(sp2.use_count() == 2);
19+
20+
CHECK_THROWS_AS(sp1.exclusive_shared_to_unique(), m::invalid_state);
21+
CHECK(*sp1 == 42);
22+
CHECK(*sp2 == 42);
23+
// sp2 goes out of scope
24+
}
25+
26+
CHECK(sp1.use_count() == 1);
27+
auto uptr = sp1.exclusive_shared_to_unique();
28+
CHECK(*uptr == 42);
29+
CHECK(sp1.use_count() == 0);
30+
CHECK(sp1.get() == nullptr);
31+
32+
unsharable_shared_ptr<int> Null(nullptr);
33+
CHECK(Null.use_count() == 0);
34+
auto null_uptr = Null.exclusive_shared_to_unique();
35+
CHECK(null_uptr.get() == nullptr);
36+
}
37+
38+
TEST_CASE("make_unsharable_shared", "[core][util]")
39+
{
40+
SECTION("int")
41+
{
42+
auto sp1 = make_unsharable_shared<int>(42);
43+
CHECK(sp1.use_count() == 1);
44+
CHECK(*sp1 == 42);
45+
46+
auto uptr = sp1.exclusive_shared_to_unique();
47+
CHECK(*uptr == 42);
48+
CHECK(sp1.use_count() == 0);
49+
CHECK(sp1.get() == nullptr);
50+
}
51+
52+
SECTION("POD")
53+
{
54+
using namespace std::string_literals;
55+
struct POD { int i; float f; const char *str; };
56+
57+
auto sp1 = make_unsharable_shared<POD>(42, 3.14f, "Hello, World!");
58+
CHECK(sp1.use_count() == 1);
59+
CHECK(sp1->i == 42);
60+
CHECK(sp1->f == 3.14f);
61+
CHECK(sp1->str == "Hello, World!"s);
62+
63+
auto uptr = sp1.exclusive_shared_to_unique();
64+
CHECK(uptr->i == 42);
65+
CHECK(uptr->f == 3.14f);
66+
CHECK(uptr->str == "Hello, World!"s);
67+
CHECK(sp1.use_count() == 0);
68+
CHECK(sp1.get() == nullptr);
69+
}
70+
}

0 commit comments

Comments
 (0)