Skip to content

Commit 68cc87f

Browse files
committed
add unique_ref
1 parent da5ca79 commit 68cc87f

File tree

4 files changed

+305
-4
lines changed

4 files changed

+305
-4
lines changed

src/utki/shared_ref.hpp

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -51,16 +51,13 @@ namespace utki {
5151
* This is considered okay. Because implicit moving occurs in temporary context where
5252
* it is not possible to dereference moved-from instance. In case of user explicitly using
5353
* std::move() it is possible to dereference moved-from instance, and for that it is
54-
* suggested to rely on lint tools (e.g. clang-tidy) to catch such misuses of shared_ref.
54+
* suggested to rely on lint tools (e.g. clang-tidy) to catch such misuses of shared_ref (use after move).
5555
*
5656
* @tparam object_type - type pointed by the pointer.
5757
*/
5858
template <class object_type>
5959
class shared_ref
6060
{
61-
template <typename other_object_type, typename... arguments_type>
62-
friend shared_ref<other_object_type> make_shared(arguments_type&&... args);
63-
6461
template <typename dst_type, typename src_type>
6562
friend shared_ref<dst_type> dynamic_reference_cast(const shared_ref<src_type>& r);
6663

src/utki/unique_ref.hpp

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
/*
2+
The MIT License (MIT)
3+
4+
utki - Utility Kit for C++.
5+
6+
Copyright (c) 2015-2025 Ivan Gagis <igagis@gmail.com>
7+
8+
Permission is hereby granted, free of charge, to any person obtaining a copy
9+
of this software and associated documentation files (the "Software"), to deal
10+
in the Software without restriction, including without limitation the rights
11+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12+
copies of the Software, and to permit persons to whom the Software is
13+
furnished to do so, subject to the following conditions:
14+
15+
The above copyright notice and this permission notice shall be included in all
16+
copies or substantial portions of the Software.
17+
18+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24+
SOFTWARE.
25+
*/
26+
27+
/* ================ LICENSE END ================ */
28+
29+
#pragma once
30+
31+
#include <memory>
32+
33+
#include "debug.hpp"
34+
35+
namespace utki {
36+
37+
/**
38+
* @brief Unique pointer which cannot be null.
39+
* unqiue_ref is same as std::unique_ptr except that it practically is never null.
40+
* Objects have to be created with utki::make_unique() in order to be managed by unqiue_ref.
41+
* The unique_ref is implemented as a wrapper around std::unique_ptr.
42+
*
43+
* unique_ref mimics the API provided by std::reference_wrapper.
44+
*
45+
* unique_ref doesn't provide default constructor.
46+
*
47+
* unique_ref provides move constructor and move-assignment operator
48+
* which will leave the moved-from unique_ref instance in invalid state.
49+
* This is considered okay. Because implicit moving occurs in temporary context where
50+
* it is not possible to dereference moved-from instance. In case of user explicitly using
51+
* std::move() it is possible to dereference moved-from instance, and for that it is
52+
* suggested to rely on lint tools (e.g. clang-tidy) to catch such misuses of unique_ref (use after move).
53+
*
54+
* @tparam object_type - type pointed by the pointer.
55+
*/
56+
template <typename object_type>
57+
class unique_ref
58+
{
59+
template <typename other_object_type>
60+
friend class unique_ref;
61+
62+
std::unique_ptr<object_type> p;
63+
64+
public:
65+
/**
66+
* @brief Construct a new unique_ref from non-null std::unique_ptr.
67+
* If DEBUG macro is defined, it will assert in case passed in pointer is null.
68+
* @param ptr - std::unique_ptr pointer to initialize the unique_ref.
69+
* Must not be null, otherwise it causes undefined behaviour.
70+
*/
71+
explicit unique_ref(std::unique_ptr<object_type> ptr) :
72+
p(std::move(ptr)){ASSERT(this->p)}
73+
74+
// there should be no default constructor, as unique_ref cannot be nullptr
75+
unique_ref() = delete;
76+
77+
unique_ref(const unique_ref&) = delete;
78+
unique_ref& operator=(const unique_ref&) = delete;
79+
80+
unique_ref(unique_ref&& r) = default;
81+
unique_ref& operator=(unique_ref&& r) = default;
82+
83+
~unique_ref() = default;
84+
85+
/**
86+
* @brief Constructor for automatic conversion to convertible type.
87+
* @param sr - unique_ref to convert.
88+
*/
89+
template < //
90+
typename other_object_type,
91+
typename std::enable_if_t< //
92+
std::is_convertible_v< //
93+
other_object_type*,
94+
object_type*>,
95+
bool> = true>
96+
unique_ref(unique_ref<other_object_type>&& sr) noexcept :
97+
p(std::move(sr.p))
98+
{
99+
ASSERT(this->p)
100+
}
101+
102+
/**
103+
* @brief Get reference to the object.
104+
*
105+
* @return Reference to the object.
106+
*/
107+
constexpr object_type& get() const noexcept
108+
{
109+
ASSERT(this->p)
110+
return *this->p;
111+
}
112+
113+
/**
114+
* @brief Convert to the reference to the object.
115+
* @return Reference to the object.
116+
*/
117+
constexpr operator object_type&() const noexcept
118+
{
119+
return this->get();
120+
}
121+
};
122+
123+
/**
124+
* @brief Create instance of an object managed by unique_ref.
125+
*
126+
* @tparam object_type - object type.
127+
* @tparam arguments_type - pack of object's constructor argument types.
128+
* @param args - object's constructor arguments.
129+
* @return unique_ref<object_type> to the newly created object.
130+
*/
131+
template <typename object_type, typename... arguments_type>
132+
unique_ref<object_type> make_unique(arguments_type&&... args)
133+
{
134+
return unique_ref<object_type>(std::make_unique<object_type>(std::forward<arguments_type>(args)...));
135+
}
136+
} // namespace utki

tests/unit/src/shared_ref.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ const tst::set set("shared_ref", [](tst::suite& suite) {
4646
" because shared_ptr can be nullptr, but shared_ref cannot"
4747
);
4848

49+
static_assert(std::is_copy_constructible_v<utki::shared_ref<std::string>>, "shared_ref must be copy constructible");
4950
static_assert(std::is_move_constructible_v<utki::shared_ref<std::string>>, "shared_ref must be move constructible");
5051

5152
suite.add("constructor__shared_ptr", []() {

tests/unit/src/unique_ref.cpp

Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
#include <tst/check.hpp>
2+
#include <tst/set.hpp>
3+
#include <utki/unique_ref.hpp>
4+
5+
namespace {
6+
const std::string etalon = "hello world!";
7+
8+
struct a0 {
9+
int a_0;
10+
11+
a0(int a) :
12+
a_0(a)
13+
{}
14+
15+
a0(const a0&) = default;
16+
a0& operator=(const a0&) = default;
17+
18+
a0(a0&&) = default;
19+
a0& operator=(a0&&) = default;
20+
21+
virtual ~a0() = default;
22+
};
23+
24+
struct a1 : public a0 {
25+
int a_1;
26+
27+
a1(int a) :
28+
a0(a),
29+
a_1(a)
30+
{}
31+
};
32+
} // namespace
33+
34+
namespace {
35+
const tst::set set("unique_ref", [](tst::suite& suite) {
36+
static_assert(
37+
!std::is_constructible_v<utki::unique_ref<std::string>>,
38+
"unique_ref must not be default constructible"
39+
" because unique_ref cannot be nullptr"
40+
);
41+
42+
static_assert(
43+
!std::is_convertible_v<std::unique_ptr<std::string>, utki::unique_ref<std::string>>,
44+
"unique_ptr must not be convertible to unique_ref"
45+
" because shared_ptr can be nullptr, but unique_ref cannot"
46+
);
47+
48+
static_assert(
49+
!std::is_copy_constructible_v<utki::unique_ref<std::string>>,
50+
"unique_ref must not be copy constructible"
51+
);
52+
53+
static_assert(std::is_move_constructible_v<utki::unique_ref<std::string>>, "unique_ref must be move constructible");
54+
55+
suite.add("constructor__unique_ptr", []() {
56+
auto up = std::make_unique<a1>(3);
57+
58+
tst::check_eq(up->a_1, 3, SL);
59+
60+
auto ur = utki::unique_ref(std::move(up));
61+
62+
tst::check(!up, SL);
63+
tst::check_eq(ur.get().a_1, 3, SL);
64+
});
65+
66+
suite.add("get", []() {
67+
utki::unique_ref<std::string> ur = utki::make_unique<std::string>(etalon);
68+
69+
tst::check_eq(ur.get(), etalon, SL);
70+
});
71+
72+
suite.add("const_get", []() {
73+
const utki::unique_ref<std::string> ur = utki::make_unique<std::string>(etalon);
74+
75+
tst::check_eq(ur.get(), etalon, SL);
76+
77+
ur.get().append("!");
78+
79+
tst::check_eq(etalon + "!", ur.get(), SL);
80+
});
81+
82+
suite.add("operator_convert_to_reference", []() {
83+
utki::unique_ref<std::string> ur = utki::make_unique<std::string>(etalon);
84+
85+
[](std::string& s) {
86+
tst::check_eq(s, etalon, SL);
87+
}(ur);
88+
89+
[](const std::string& s) {
90+
tst::check_eq(s, etalon, SL);
91+
}(ur);
92+
});
93+
94+
suite.add("operator_convert_to_reference_const", []() {
95+
utki::unique_ref<const std::string> ur = utki::make_unique<std::string>(etalon);
96+
97+
[](const std::string& s) {
98+
tst::check_eq(s, etalon, SL);
99+
}(ur);
100+
});
101+
102+
suite.add("const_autocast", []() {
103+
utki::unique_ref<const std::string> sr = utki::make_unique<std::string>(etalon);
104+
105+
tst::check_eq(sr.get(), etalon, SL);
106+
});
107+
108+
suite.add("downcast", []() {
109+
utki::unique_ref<a0> sr = utki::make_unique<a1>(2);
110+
111+
tst::check_eq(sr.get().a_0, 2, SL);
112+
});
113+
114+
suite.add("const_downcast", []() {
115+
utki::unique_ref<const a0> sr = utki::make_unique<a1>(2);
116+
117+
tst::check_eq(sr.get().a_0, 2, SL);
118+
});
119+
120+
suite.add("move_constructor", []() {
121+
auto a = utki::make_unique<a1>(13);
122+
123+
tst::check_eq(a.get().a_0, 13, SL);
124+
125+
decltype(a) b(std::move(a));
126+
127+
tst::check_eq(b.get().a_0, 13, SL);
128+
});
129+
130+
suite.add("move_assignment", []() {
131+
auto a = utki::make_unique<a1>(13);
132+
133+
tst::check_eq(a.get().a_0, 13, SL);
134+
135+
auto b = utki::make_unique<a1>(10);
136+
137+
tst::check_eq(b.get().a_0, 10, SL);
138+
139+
b = std::move(a);
140+
141+
tst::check_eq(b.get().a_0, 13, SL);
142+
});
143+
144+
suite.add("move_constructible_and_move_assignable", []() {
145+
std::vector<utki::unique_ref<a0>> vec;
146+
vec.emplace_back(utki::make_unique<a1>(1));
147+
vec.emplace_back(utki::make_unique<a1>(2));
148+
vec.emplace_back(utki::make_unique<a1>(3));
149+
150+
tst::check_eq(vec[0].get().a_0, 1, SL);
151+
tst::check_eq(vec[1].get().a_0, 2, SL);
152+
tst::check_eq(vec[2].get().a_0, 3, SL);
153+
154+
// std::rotate requires container element type to be
155+
// MoveAssignable and MoveConstructible
156+
#if CFG_CPP >= 20
157+
std::ranges::rotate(vec.begin(), std::next(vec.begin()), vec.end());
158+
#else
159+
std::rotate(vec.begin(), std::next(vec.begin()), vec.end());
160+
#endif
161+
162+
tst::check_eq(vec[0].get().a_0, 2, SL);
163+
tst::check_eq(vec[1].get().a_0, 3, SL);
164+
tst::check_eq(vec[2].get().a_0, 1, SL);
165+
});
166+
});
167+
} // namespace

0 commit comments

Comments
 (0)