Skip to content

Commit f79ca93

Browse files
committed
Initial commit
1 parent dd5bb4b commit f79ca93

13 files changed

+889
-0
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
build/

CMakeLists.txt

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
cmake_minimum_required(VERSION 3.8)
2+
3+
project(oup LANGUAGES CXX VERSION 1.0)
4+
5+
add_library(oup INTERFACE)
6+
add_library(oup::oup ALIAS oup)
7+
set_target_properties(oup PROPERTIES EXPORT_NAME oup::oup)
8+
9+
target_sources(oup INTERFACE ${PROJECT_SOURCE_DIR}/include/oup/observable_unique_ptr.hpp)
10+
target_include_directories(oup INTERFACE ${PROJECT_SOURCE_DIR}/include)
11+
target_compile_features(oup INTERFACE cxx_std_17)
12+
13+
install(FILES ${PROJECT_SOURCE_DIR}/include/oup/observable_unique_ptr.hpp DESTINATION include/oup)
14+
install(TARGETS oup EXPORT oup)
15+
16+
if (OUP_DO_TEST)
17+
enable_testing()
18+
add_subdirectory(tests)
19+
endif()

LICENSE

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2021 Corentin Schreiber
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

include/oup/observable_unique_ptr.hpp

+278
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,278 @@
1+
#ifndef OBSERVABLE_UNIQUE_PTR_INCLUDED
2+
#define OBSERVABLE_UNIQUE_PTR_INCLUDED
3+
4+
#include <memory>
5+
6+
namespace oup {
7+
8+
/// Simple deleter, suitable for objects allocated with new.
9+
struct default_deleter {
10+
template<typename T>
11+
void operator() (T* ptr) { delete ptr; }
12+
13+
void operator() (std::nullptr_t) {}
14+
};
15+
16+
/// std::unique_ptr that can be observed by std::weak_ptr
17+
/** This smart pointer mimics the interface of std::unique_ptr, in that
18+
* it is movable but not copiable. The smart pointer holds exclusive
19+
* (unique) ownership of the pointed object.
20+
*
21+
* The main difference with std::unique_ptr is that it allows creating
22+
* std::weak_ptr instances to observe the lifetime of the pointed object,
23+
* as one would do with std::shared_ptr. The price to pay, compared to a
24+
* standard std::unique_ptr, is the additional heap allocation of the
25+
* reference-counting control block, which utils::make_observable_unique()
26+
* will optimise as a single heap allocation with the pointed object (as
27+
* std::make_shared() does for std::shared_ptr).
28+
*
29+
* Other notable differences (either limitations imposed by the current
30+
* implementation, or features not implemented simply because of lack of
31+
* motivation):
32+
* - observable_unique_ptr does not support arrays.
33+
* - observable_unique_ptr does not allow custom allocators.
34+
* - observable_unique_ptr does not have a release() function to let go of
35+
* the ownership.
36+
* - observable_unique_ptr allows moving from other observable_unique_ptr
37+
* only if the deleter type is exactly the same, while std::unique_ptr
38+
* allows moving from a convertible deleter.
39+
* - observable_unique_ptr may not own a deleter instance; if in doubt, check
40+
* has_deleter() before calling get_deleter(), or use try_get_deleter().
41+
* - a moved-from observable_unique_ptr will not own a deleter instance.
42+
*/
43+
template<typename T, typename Deleter = default_deleter>
44+
class observable_unique_ptr : private std::shared_ptr<T> {
45+
private:
46+
/// Construct from shared_ptr.
47+
/** \param value The shared_ptr to take ownership from
48+
* \note This is private since the use of std::shared_ptr is
49+
* an implementation detail.
50+
*/
51+
explicit observable_unique_ptr(std::shared_ptr<T>&& value) noexcept :
52+
std::shared_ptr<T>(std::move(value)) {}
53+
54+
// Friendship is required for conversions.
55+
template<typename U, typename D>
56+
friend class observable_unique_ptr;
57+
58+
public:
59+
// Import members from std::shared_ptr
60+
using typename std::shared_ptr<T>::element_type;
61+
using typename std::shared_ptr<T>::weak_type;
62+
63+
using std::shared_ptr<T>::get;
64+
using std::shared_ptr<T>::operator*;
65+
using std::shared_ptr<T>::operator->;
66+
using std::shared_ptr<T>::operator bool;
67+
68+
// Define member types for compatibility with std::unique_ptr
69+
using pointer = element_type*;
70+
using deleter_type = Deleter;
71+
72+
/// Default constructor (null pointer).
73+
observable_unique_ptr() noexcept :
74+
std::shared_ptr<T>(nullptr, Deleter{}) {}
75+
76+
/// Construct a null pointer.
77+
observable_unique_ptr(std::nullptr_t) noexcept :
78+
std::shared_ptr<T>(nullptr, Deleter{}) {}
79+
80+
/// Construct a null pointer with custom deleter.
81+
observable_unique_ptr(std::nullptr_t, Deleter deleter) noexcept :
82+
std::shared_ptr<T>(nullptr, std::move(deleter)) {}
83+
84+
/// Explicit ownership capture of a raw pointer.
85+
/** \param value The raw pointer to take ownership of
86+
* \note Do *not* manually delete this raw pointer after the
87+
* observable_unique_ptr is created. If possible, prefer
88+
* using make_observable_unique() instead of this constructor.
89+
*/
90+
explicit observable_unique_ptr(T* value) : std::shared_ptr<T>(value, Deleter{}) {}
91+
92+
/// Explicit ownership capture of a raw pointer, with customer deleter.
93+
/** \param value The raw pointer to take ownership of
94+
* \param deleter The deleter object to use
95+
* \note Do *not* manually delete this raw pointer after the
96+
* observable_unique_ptr is created. If possible, prefer
97+
* using make_observable_unique() instead of this constructor.
98+
*/
99+
explicit observable_unique_ptr(T* value, Deleter deleter) :
100+
std::shared_ptr<T>(value, std::move(deleter)) {}
101+
102+
/// Transfer ownership by implicit casting
103+
/** \param value The pointer to take ownership from
104+
* \note After this observable_unique_ptr is created, the source
105+
* pointer is set to null and looses ownership.
106+
*/
107+
template<typename U>
108+
observable_unique_ptr(observable_unique_ptr<U,Deleter>&& value) noexcept :
109+
std::shared_ptr<T>(std::move(static_cast<std::shared_ptr<U>&>(value))) {}
110+
111+
/// Transfer ownership by explicit casting
112+
/** \param manager The smart pointer to take ownership from
113+
* \param value The casted pointer value to take ownership of
114+
* \note After this observable_unique_ptr is created, the source
115+
* pointer is set to null and looses ownership.
116+
*/
117+
template<typename U>
118+
observable_unique_ptr(observable_unique_ptr<U,Deleter>&& manager, T* value) noexcept :
119+
std::shared_ptr<T>(std::move(manager), value) {
120+
manager.std::template shared_ptr<U>::reset();
121+
}
122+
123+
/// Transfer ownership by implicit casting
124+
/** \param value The pointer to take ownership from
125+
* \note After this observable_unique_ptr is created, the source
126+
* pointer is set to null and looses ownership.
127+
*/
128+
template<typename U>
129+
observable_unique_ptr& operator=(observable_unique_ptr<U,Deleter>&& value) noexcept {
130+
std::shared_ptr<T>::operator=(std::move(static_cast<std::shared_ptr<U>&>(value)));
131+
return *this;
132+
}
133+
134+
// Movable
135+
observable_unique_ptr(observable_unique_ptr&&) noexcept = default;
136+
observable_unique_ptr& operator=(observable_unique_ptr&&) noexcept = default;
137+
138+
// Non-copyable
139+
observable_unique_ptr(const observable_unique_ptr&) = delete;
140+
observable_unique_ptr& operator=(const observable_unique_ptr&) = delete;
141+
142+
/// Checks if this pointer has a custom deleter.
143+
/** \return 'true' if a custom deleter is used, 'false' otherwise.
144+
*/
145+
bool has_deleter() const noexcept {
146+
if constexpr (std::is_same_v<Deleter, oup::default_deleter>) {
147+
return false;
148+
} else {
149+
return std::get_deleter<Deleter>(*this) != nullptr;
150+
}
151+
}
152+
153+
/// Returns the deleter object which would be used for destruction of the managed object.
154+
/** \return The deleter
155+
* \note Using the return value of this function if has_deleter() returns 'false' will cause
156+
* undefined behavior.
157+
*/
158+
Deleter& get_deleter() noexcept {
159+
return *std::get_deleter<Deleter>(*this);
160+
}
161+
162+
/// Returns the deleter object which would be used for destruction of the managed object.
163+
/** \return The deleter
164+
* \note Using the return value of this function if has_deleter() returns 'false' will cause
165+
* undefined behavior.
166+
*/
167+
const Deleter& get_deleter() const noexcept {
168+
return *std::get_deleter<Deleter>(*this);
169+
}
170+
171+
/// Returns the deleter object which would be used for destruction of the managed object.
172+
/** \return The deleter, or nullptr if no deleter exists
173+
*/
174+
Deleter* try_get_deleter() noexcept {
175+
return std::get_deleter<Deleter>(*this);
176+
}
177+
178+
/// Returns the deleter object which would be used for destruction of the managed object.
179+
/** \return The deleter, or nullptr if no deleter exists
180+
*/
181+
const Deleter* try_get_deleter() const noexcept {
182+
return std::get_deleter<Deleter>(*this);
183+
}
184+
185+
/// Swap the content of this pointer with that of another pointer.
186+
/** \param other The other pointer to swap with
187+
*/
188+
void swap(observable_unique_ptr& other) noexcept {
189+
std::shared_ptr<T>::swap(other);
190+
}
191+
192+
/// Replaces the managed object with a null pointer.
193+
/** \param ptr A nullptr_t instance
194+
*/
195+
void reset(std::nullptr_t ptr = nullptr) noexcept {
196+
if constexpr (std::is_same_v<Deleter, oup::default_deleter>) {
197+
operator=(observable_unique_ptr{ptr});
198+
} else {
199+
if (auto* deleter = try_get_deleter()) {
200+
operator=(observable_unique_ptr{ptr, Deleter{*deleter}});
201+
} else {
202+
operator=(observable_unique_ptr{ptr, Deleter{}});
203+
}
204+
}
205+
}
206+
207+
/// Replaces the managed object.
208+
/** \param p A nullptr_t instance
209+
*/
210+
void reset(T* ptr) noexcept {
211+
if constexpr (std::is_same_v<Deleter, oup::default_deleter>) {
212+
operator=(observable_unique_ptr{ptr});
213+
} else {
214+
if (auto* deleter = try_get_deleter()) {
215+
operator=(observable_unique_ptr{ptr, Deleter{*deleter}});
216+
} else {
217+
operator=(observable_unique_ptr{ptr, Deleter{}});
218+
}
219+
}
220+
}
221+
222+
/// Replaces the managed object (with custom deleter).
223+
/** \param ptr A pointer to the new object to own
224+
* \param deleter The new custom deleter instance to use
225+
* \note After this call, any previously owner object will be deleted
226+
*/
227+
void reset(T* ptr, Deleter deleter) noexcept {
228+
operator=(observable_unique_ptr{ptr, std::move(deleter)});
229+
}
230+
231+
template<typename U, typename ... Args>
232+
friend observable_unique_ptr<U> make_observable_unique(Args&& ... args);
233+
};
234+
235+
/// Create a new observable_unique_ptr with a newly constructed object.
236+
/** \param args Arguments to construt the new object
237+
* \return The new observable_unique_ptr
238+
*/
239+
template<typename T, typename ... Args>
240+
observable_unique_ptr<T> make_observable_unique(Args&& ... args) {
241+
return observable_unique_ptr<T>(std::make_shared<T>(std::forward<Args>(args)...));
242+
}
243+
244+
template<typename T, typename Deleter>
245+
bool operator== (const observable_unique_ptr<T,Deleter>& value, std::nullptr_t) noexcept {
246+
return value.get() == nullptr;
247+
}
248+
249+
template<typename T, typename Deleter>
250+
bool operator== (std::nullptr_t, const observable_unique_ptr<T,Deleter>& value) noexcept {
251+
return value.get() == nullptr;
252+
}
253+
254+
template<typename T, typename Deleter>
255+
bool operator!= (const observable_unique_ptr<T,Deleter>& value, std::nullptr_t) noexcept {
256+
return value.get() != nullptr;
257+
}
258+
259+
template<typename T, typename Deleter>
260+
bool operator!= (std::nullptr_t, const observable_unique_ptr<T,Deleter>& value) noexcept {
261+
return value.get() != nullptr;
262+
}
263+
264+
template<typename T, typename U, typename Deleter>
265+
bool operator== (const observable_unique_ptr<T,Deleter>& first,
266+
const observable_unique_ptr<U,Deleter> second) noexcept {
267+
return first.get() == second.get();
268+
}
269+
270+
template<typename T, typename U, typename Deleter>
271+
bool operator!= (const observable_unique_ptr<T,Deleter>& first,
272+
const observable_unique_ptr<U,Deleter>& second) noexcept {
273+
return first.get() != second.get();
274+
}
275+
276+
}
277+
278+
#endif

tests/CMakeLists.txt

+47
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
include(FetchContent)
2+
3+
FetchContent_Declare(
4+
Catch2
5+
GIT_REPOSITORY https://github.com/catchorg/Catch2.git
6+
GIT_TAG v3.0.0-preview3
7+
)
8+
9+
FetchContent_MakeAvailable(Catch2)
10+
11+
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${Catch2_SOURCE_DIR}/extras")
12+
13+
add_executable(oup_tests ${PROJECT_SOURCE_DIR}/tests/runtime_tests.cpp)
14+
target_link_libraries(oup_tests PRIVATE Catch2::Catch2WithMain)
15+
target_link_libraries(oup_tests PRIVATE oup::oup)
16+
17+
include(CTest)
18+
include(Catch)
19+
catch_discover_tests(oup_tests)
20+
21+
# Compile-time error tests
22+
set(CMAKE_TRY_COMPILE_TARGET_TYPE EXECUTABLE)
23+
message(STATUS "Running compile-time tests...")
24+
25+
function(run_compile_test TEST_NAME TEST_FILE EXPECTED)
26+
try_compile(COMPILE_TEST_RESULT
27+
${PROJECT_SOURCE_DIR}/tests
28+
${PROJECT_SOURCE_DIR}/tests/${TEST_FILE}
29+
CMAKE_FLAGS
30+
"-DINCLUDE_DIRECTORIES=${PROJECT_SOURCE_DIR}/include"
31+
"-DCMAKE_CXX_STANDARD=17")
32+
33+
if (COMPILE_TEST_RESULT STREQUAL EXPECTED)
34+
message(STATUS "Test ${TEST_NAME} passed.")
35+
else()
36+
message(SEND_ERROR "FAILED test: ${TEST_NAME}, expected ${EXPECTED} and got ${COMPILE_TEST_RESULT}")
37+
endif()
38+
endfunction()
39+
40+
run_compile_test("does_compilation_works" compile_test_good.cpp TRUE)
41+
run_compile_test("is_copy_constructor_allowed" compile_test_copy_const.cpp FALSE)
42+
run_compile_test("is_copy_assignment_allowed" compile_test_copy_assign.cpp FALSE)
43+
run_compile_test("is_implicit_constructor_base_to_derived_allowed_acquire" compile_test_implicit_const_base_to_derived1.cpp FALSE)
44+
run_compile_test("is_implicit_constructor_base_to_derived_allowed_move" compile_test_implicit_const_base_to_derived2.cpp FALSE)
45+
run_compile_test("is_implicit_constructor_base_to_derived_allowed_move_with_deleter" compile_test_implicit_const_base_to_derived3.cpp FALSE)
46+
47+
message(STATUS "Running compile-time tests ended.")

tests/compile_test_copy_assign.cpp

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
#include "tests_common.hpp"
2+
3+
int main() {
4+
test_ptr ptr_orig(new test_object);
5+
test_ptr ptr;
6+
ptr = ptr_orig;
7+
return 0;
8+
}

tests/compile_test_copy_const.cpp

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
#include "tests_common.hpp"
2+
3+
int main() {
4+
test_ptr ptr_orig(new test_object);
5+
test_ptr ptr(ptr_orig);
6+
return 0;
7+
}

tests/compile_test_good.cpp

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
#include "tests_common.hpp"
2+
3+
int main() {
4+
test_ptr ptr_orig(new test_object);
5+
return 0;
6+
}

0 commit comments

Comments
 (0)