Skip to content

Commit 278ab88

Browse files
Bring our own make_unique
1 parent ea59e7d commit 278ab88

File tree

3 files changed

+155
-38
lines changed

3 files changed

+155
-38
lines changed

src/bsoncxx/stdx/make_unique.hpp

Lines changed: 109 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -14,52 +14,124 @@
1414

1515
#pragma once
1616

17-
#include <bsoncxx/config/prelude.hpp>
18-
19-
#if defined(BSONCXX_POLY_USE_MNMLSTC)
20-
21-
#include <core/memory.hpp>
22-
23-
namespace bsoncxx {
24-
BSONCXX_INLINE_NAMESPACE_BEGIN
25-
namespace stdx {
26-
27-
using ::core::make_unique;
28-
29-
} // namespace stdx
30-
BSONCXX_INLINE_NAMESPACE_END
31-
} // namespace bsoncxx
32-
33-
#elif defined(BSONCXX_POLY_USE_BOOST)
34-
35-
#include <boost/smart_ptr/make_unique.hpp>
36-
37-
namespace bsoncxx {
38-
BSONCXX_INLINE_NAMESPACE_BEGIN
39-
namespace stdx {
40-
41-
using ::boost::make_unique;
42-
43-
} // namespace stdx
44-
BSONCXX_INLINE_NAMESPACE_END
45-
} // namespace bsoncxx
46-
47-
#elif __cplusplus >= 201402L || (defined(_MSVC_LANG) && _MSVC_LANG >= 201402L)
48-
17+
#include <cstddef>
4918
#include <memory>
19+
#include <type_traits>
20+
#include <utility>
21+
22+
#include <bsoncxx/config/prelude.hpp>
5023

5124
namespace bsoncxx {
5225
BSONCXX_INLINE_NAMESPACE_BEGIN
5326
namespace stdx {
5427

55-
using ::std::make_unique;
28+
namespace detail {
29+
30+
// Switch backend of make_unique by the type we are creating.
31+
// It would be easier to 'if constexpr' on whether we are an array and whether to direct-init or
32+
// value-init, but we don't have if-constexpr and we need it to guard against an uterance of a
33+
// possibly-illegal 'new' expression.
34+
template <typename T>
35+
struct make_unique_impl {
36+
// For make_unique:
37+
template <typename... Args,
38+
// Guard on constructible-from:
39+
typename = decltype(new T(std::declval<Args>()...))>
40+
static std::unique_ptr<T> make(std::true_type /* direct-init */, Args&&... args) {
41+
return std::unique_ptr<T>(new T(std::forward<Args>(args)...));
42+
}
43+
44+
// For make_unique_for_overwrite:
45+
template <typename U = T,
46+
// Guard on whether T is value-initializable:
47+
// (Hide behind a deduced 'U' to defer the evaluation of
48+
// this default template argument until overload resolution)
49+
typename = decltype(new U)>
50+
static std::unique_ptr<T> make(std::false_type /* value-init */) {
51+
return std::unique_ptr<T>(new T);
52+
}
53+
};
54+
55+
// For unbounded arrays:
56+
template <typename Elem>
57+
struct make_unique_impl<Elem[]> {
58+
template <typename ShouldDirectInit,
59+
// Guard on whether the new-expression will be legal:
60+
typename = decltype(new Elem[std::declval<std::size_t>()])>
61+
static std::unique_ptr<Elem[]> make(ShouldDirectInit, std::size_t count) {
62+
// These can share a function via a plain if, because both new expressions
63+
// must be semantically valid
64+
if (ShouldDirectInit()) {
65+
return std::unique_ptr<Elem[]>(new Elem[count]());
66+
} else {
67+
return std::unique_ptr<Elem[]>(new Elem[count]);
68+
}
69+
}
70+
};
71+
72+
// Bounded arrays are disallowed:
73+
template <typename Elem, std::size_t N>
74+
struct make_unique_impl<Elem[N]> {};
75+
76+
// References are nonsense:
77+
template <typename T>
78+
struct make_unique_impl<T&> {};
79+
80+
// References are nonsense:
81+
template <typename T>
82+
struct make_unique_impl<T&&> {};
83+
84+
} // namespace detail
85+
86+
/**
87+
* @brief Create a new std::unique_ptr that points-to the given type, direct-initialized based on
88+
* the given arguments.
89+
*
90+
* @tparam T Any non-array object type or any array of unknown bound.
91+
* @param args If T is a non-array object type, these are the constructor arguments used to
92+
* direct-initialize the object. If T is an array of unknown bound, then the sole argument must be a
93+
* single std::size_t that specifies the number of objects to allocate in the array.
94+
*
95+
* Requires:
96+
* - If T is an array of unknown bounds, then args... must be a single size_t and the element type
97+
* of T must be value-initializable.
98+
* - Otherwise, if T is a non-array object type, then T must be direct-initializable with arguments
99+
* `args...`
100+
* - Otherwise, this function is excluded from overload resolution.
101+
*/
102+
template <typename T,
103+
typename... Args,
104+
typename Impl = detail::make_unique_impl<T>,
105+
typename = decltype(Impl::make(std::true_type{}, std::declval<Args>()...))>
106+
std::unique_ptr<T> make_unique(Args&&... args) {
107+
return Impl::make(std::true_type{}, std::forward<Args>(args)...);
108+
}
109+
110+
/**
111+
* @brief Create a new std::unique_ptr that points-to a default-initialized instance of the given
112+
* type.
113+
*
114+
* @tparam T A non-array object type or an array of unknown bound
115+
* @param args If T is an object type, then args... must no arguments are allowed.
116+
* If T is an array of unknown bound, then args... must be a single size_t specifying
117+
* the length of the array to allocate.
118+
*
119+
* Requires:
120+
* - T must be value-initializable
121+
* - If T is an array of unknown bounds, then args... must be a single size_t
122+
* - Otherwise, if T is a non-array object type, args... must be empty
123+
* - Otherwise, this function is excluded from overload resolution
124+
*/
125+
template <typename T,
126+
typename... Args,
127+
typename Impl = detail::make_unique_impl<T>,
128+
typename = decltype(Impl::make(std::false_type{}, std::declval<Args>()...))>
129+
std::unique_ptr<T> make_unique_for_overwrite(Args&&... args) {
130+
return Impl::make(std::false_type{}, std::forward<Args>(args)...);
131+
}
56132

57133
} // namespace stdx
58134
BSONCXX_INLINE_NAMESPACE_END
59135
} // namespace bsoncxx
60136

61-
#else
62-
#error "Cannot find a valid polyfill for make_unique"
63-
#endif
64-
65137
#include <bsoncxx/config/postlude.hpp>

src/bsoncxx/test/CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ include_directories(
2020
${MONGO_CXX_DRIVER_SOURCE_DIR}/src/third_party/catch/include
2121
)
2222

23-
file (GLOB src_bsoncxx_test_DIST_cpps RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} *.cpp)
23+
file (GLOB src_bsoncxx_test_DIST_cpps CONFIGURE_DEPENDS RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} *.cpp)
2424

2525
add_executable(test_bson
2626
${THIRD_PARTY_SOURCE_DIR}/catch/main.cpp

src/bsoncxx/test/make_unique.test.cpp

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
#include <algorithm>
2+
#include <catch.hpp>
3+
#include <cstdint>
4+
#include <new>
5+
6+
#include <bsoncxx/stdx/make_unique.hpp>
7+
8+
namespace {
9+
10+
struct something {
11+
something(int val) : value(val) {}
12+
int value;
13+
};
14+
15+
TEST_CASE("Create a unique_ptr") {
16+
auto ptr = bsoncxx::stdx::make_unique<int>(12);
17+
CHECKED_IF(ptr) {
18+
CHECK(*ptr == 12);
19+
}
20+
21+
auto thing = bsoncxx::stdx::make_unique<something>(5);
22+
CHECKED_IF(thing) {
23+
CHECK(thing->value == 5);
24+
}
25+
}
26+
27+
TEST_CASE("Create a unique_ptr<T[]>") {
28+
const int length = 12;
29+
auto ptr = bsoncxx::stdx::make_unique<int[]>(length);
30+
CHECKED_IF(ptr) {
31+
// All elements are direct-initialized, which produces '0' for `int`
32+
CHECK(ptr[0] == 0);
33+
auto res = std::equal_range(ptr.get(), ptr.get() + length, 0);
34+
CHECK(res.first == ptr.get());
35+
CHECK(res.second == (ptr.get() + length));
36+
}
37+
38+
ptr = bsoncxx::stdx::make_unique_for_overwrite<int[]>(length);
39+
std::fill_n(ptr.get(), length, 42);
40+
CHECK(std::all_of(ptr.get(), ptr.get() + length, [](int n) { return n == 42; }));
41+
42+
CHECK_THROWS_AS(bsoncxx::stdx::make_unique<int[]>(SIZE_MAX), std::bad_alloc);
43+
}
44+
45+
} // namespace

0 commit comments

Comments
 (0)