Skip to content

Commit e5ce963

Browse files
authored
[smart_holder] Unique ptr deleter roundtrip tests and fix (#4921)
* Roundtrip through unique pointer with custom deleter. Currently failing. * Ensure the custom deleter is copied back to the unique pointer. Feels like there's still a gap around the raw pointer flavour, but this at least makes the unit test of the previous commit succeed. * Add deleter roundtrip for const atyp. Currently failing, custom deleter is lost. * Fix storing deleter for const unique ptr. Unit test from the previous commit passes. * Remove SFINEA deleter assignment. At the construction of the smart holder, it is either a del_fun, or a default constructed deleter, so this complexity is unnecessary. * Clang format. * Fixes for ci. Clang 3.6 requires the extra constructors in the custom_deleter. * fix(smart_holder): Loosen requirement on deleter to be default constructible. And some other PR feedback. * fix(smart_holder): Custom deleter in unit tests traces constructions. * fix(smart_holder): Use pybind11_fail instead of assert. * fix(smart_holder): Add unit tests for the default constructible deleter. * fix(smart_holder): Use regex matching for deleter constructors in unit tests.
1 parent e02fe00 commit e5ce963

File tree

3 files changed

+132
-3
lines changed

3 files changed

+132
-3
lines changed

include/pybind11/detail/smart_holder_type_casters.h

Lines changed: 42 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -464,6 +464,27 @@ struct shared_ptr_trampoline_self_life_support {
464464
}
465465
};
466466

467+
template <typename T,
468+
typename D,
469+
typename std::enable_if<std::is_default_constructible<D>::value, int>::type = 0>
470+
inline std::unique_ptr<T, D> unique_with_deleter(T *raw_ptr, std::unique_ptr<D> &&deleter) {
471+
if (deleter == nullptr) {
472+
return std::unique_ptr<T, D>(raw_ptr);
473+
}
474+
return std::unique_ptr<T, D>(raw_ptr, std::move(*deleter));
475+
}
476+
477+
template <typename T,
478+
typename D,
479+
typename std::enable_if<!std::is_default_constructible<D>::value, int>::type = 0>
480+
inline std::unique_ptr<T, D> unique_with_deleter(T *raw_ptr, std::unique_ptr<D> &&deleter) {
481+
if (deleter == nullptr) {
482+
pybind11_fail("smart_holder_type_casters: deleter is not default constructible and no"
483+
" instance available to return.");
484+
}
485+
return std::unique_ptr<T, D>(raw_ptr, std::move(*deleter));
486+
}
487+
467488
template <typename T>
468489
struct smart_holder_type_caster_load {
469490
using holder_type = pybindit::memory::smart_holder;
@@ -589,7 +610,7 @@ struct smart_holder_type_caster_load {
589610
" std::unique_ptr.");
590611
}
591612
if (!have_holder()) {
592-
return nullptr;
613+
return unique_with_deleter<T, D>(nullptr, std::unique_ptr<D>());
593614
}
594615
throw_if_uninitialized_or_disowned_holder();
595616
throw_if_instance_is_currently_owned_by_shared_ptr();
@@ -616,13 +637,30 @@ struct smart_holder_type_caster_load {
616637
"instance cannot safely be transferred to C++.");
617638
}
618639

640+
// Temporary variable to store the extracted deleter in.
641+
std::unique_ptr<D> extracted_deleter;
642+
643+
auto *gd = std::get_deleter<pybindit::memory::guarded_delete>(holder().vptr);
644+
if (gd && gd->use_del_fun) { // Note the ensure_compatible_rtti_uqp_del<T, D>() call above.
645+
// In smart_holder_poc, a custom deleter is always stored in a guarded delete.
646+
// The guarded delete's std::function<void(void*)> actually points at the
647+
// custom_deleter type, so we can verify it is of the custom deleter type and
648+
// finally extract its deleter.
649+
using custom_deleter_D = pybindit::memory::custom_deleter<T, D>;
650+
const auto &custom_deleter_ptr = gd->del_fun.template target<custom_deleter_D>();
651+
assert(custom_deleter_ptr != nullptr);
652+
// Now that we have confirmed the type of the deleter matches the desired return
653+
// value we can extract the function.
654+
extracted_deleter = std::unique_ptr<D>(new D(std::move(custom_deleter_ptr->deleter)));
655+
}
656+
619657
// Critical transfer-of-ownership section. This must stay together.
620658
if (self_life_support != nullptr) {
621659
holder().disown();
622660
} else {
623661
holder().release_ownership();
624662
}
625-
auto result = std::unique_ptr<T, D>(raw_type_ptr);
663+
auto result = unique_with_deleter<T, D>(raw_type_ptr, std::move(extracted_deleter));
626664
if (self_life_support != nullptr) {
627665
self_life_support->activate_life_support(load_impl.loaded_v_h);
628666
} else {
@@ -1056,7 +1094,8 @@ struct smart_holder_type_caster<std::unique_ptr<T const, D>>
10561094
static handle
10571095
cast(std::unique_ptr<T const, D> &&src, return_value_policy policy, handle parent) {
10581096
return smart_holder_type_caster<std::unique_ptr<T, D>>::cast(
1059-
std::unique_ptr<T, D>(const_cast<T *>(src.release())), // Const2Mutbl
1097+
std::unique_ptr<T, D>(const_cast<T *>(src.release()),
1098+
std::move(src.get_deleter())), // Const2Mutbl
10601099
policy,
10611100
parent);
10621101
}

tests/test_class_sh_basic.cpp

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,43 @@ struct uconsumer { // unique_ptr consumer
2828
const std::unique_ptr<atyp> &rtrn_cref() const { return held; }
2929
};
3030

31+
/// Custom deleter that is default constructible.
32+
struct custom_deleter {
33+
std::string trace_txt;
34+
35+
custom_deleter() = default;
36+
explicit custom_deleter(const std::string &trace_txt_) : trace_txt(trace_txt_) {}
37+
38+
custom_deleter(const custom_deleter &other) { trace_txt = other.trace_txt + "_CpCtor"; }
39+
40+
custom_deleter &operator=(const custom_deleter &rhs) {
41+
trace_txt = rhs.trace_txt + "_CpLhs";
42+
return *this;
43+
}
44+
45+
custom_deleter(custom_deleter &&other) noexcept {
46+
trace_txt = other.trace_txt + "_MvCtorTo";
47+
other.trace_txt += "_MvCtorFrom";
48+
}
49+
50+
custom_deleter &operator=(custom_deleter &&rhs) noexcept {
51+
trace_txt = rhs.trace_txt + "_MvLhs";
52+
rhs.trace_txt += "_MvRhs";
53+
return *this;
54+
}
55+
56+
void operator()(atyp *p) const { std::default_delete<atyp>()(p); }
57+
void operator()(const atyp *p) const { std::default_delete<const atyp>()(p); }
58+
};
59+
static_assert(std::is_default_constructible<custom_deleter>::value, "");
60+
61+
/// Custom deleter that is not default constructible.
62+
struct custom_deleter_nd : custom_deleter {
63+
custom_deleter_nd() = delete;
64+
explicit custom_deleter_nd(const std::string &trace_txt_) : custom_deleter(trace_txt_) {}
65+
};
66+
static_assert(!std::is_default_constructible<custom_deleter_nd>::value, "");
67+
3168
// clang-format off
3269

3370
atyp rtrn_valu() { atyp obj{"rtrn_valu"}; return obj; }
@@ -64,6 +101,18 @@ std::unique_ptr<atyp const, sddc> rtrn_udcp() { return std::unique_ptr<atyp cons
64101
std::string pass_udmp(std::unique_ptr<atyp, sddm> obj) { return "pass_udmp:" + obj->mtxt; }
65102
std::string pass_udcp(std::unique_ptr<atyp const, sddc> obj) { return "pass_udcp:" + obj->mtxt; }
66103

104+
std::unique_ptr<atyp, custom_deleter> rtrn_udmp_del() { return std::unique_ptr<atyp, custom_deleter>(new atyp{"rtrn_udmp_del"}, custom_deleter{"udmp_deleter"}); }
105+
std::unique_ptr<atyp const, custom_deleter> rtrn_udcp_del() { return std::unique_ptr<atyp const, custom_deleter>(new atyp{"rtrn_udcp_del"}, custom_deleter{"udcp_deleter"}); }
106+
107+
std::string pass_udmp_del(std::unique_ptr<atyp, custom_deleter> obj) { return "pass_udmp_del:" + obj->mtxt + "," + obj.get_deleter().trace_txt; }
108+
std::string pass_udcp_del(std::unique_ptr<atyp const, custom_deleter> obj) { return "pass_udcp_del:" + obj->mtxt + "," + obj.get_deleter().trace_txt; }
109+
110+
std::unique_ptr<atyp, custom_deleter_nd> rtrn_udmp_del_nd() { return std::unique_ptr<atyp, custom_deleter_nd>(new atyp{"rtrn_udmp_del_nd"}, custom_deleter_nd{"udmp_deleter_nd"}); }
111+
std::unique_ptr<atyp const, custom_deleter_nd> rtrn_udcp_del_nd() { return std::unique_ptr<atyp const, custom_deleter_nd>(new atyp{"rtrn_udcp_del_nd"}, custom_deleter_nd{"udcp_deleter_nd"}); }
112+
113+
std::string pass_udmp_del_nd(std::unique_ptr<atyp, custom_deleter_nd> obj) { return "pass_udmp_del_nd:" + obj->mtxt + "," + obj.get_deleter().trace_txt; }
114+
std::string pass_udcp_del_nd(std::unique_ptr<atyp const, custom_deleter_nd> obj) { return "pass_udcp_del_nd:" + obj->mtxt + "," + obj.get_deleter().trace_txt; }
115+
67116
// clang-format on
68117

69118
// Helpers for testing.
@@ -130,6 +179,18 @@ TEST_SUBMODULE(class_sh_basic, m) {
130179
m.def("pass_udmp", pass_udmp);
131180
m.def("pass_udcp", pass_udcp);
132181

182+
m.def("rtrn_udmp_del", rtrn_udmp_del);
183+
m.def("rtrn_udcp_del", rtrn_udcp_del);
184+
185+
m.def("pass_udmp_del", pass_udmp_del);
186+
m.def("pass_udcp_del", pass_udcp_del);
187+
188+
m.def("rtrn_udmp_del_nd", rtrn_udmp_del_nd);
189+
m.def("rtrn_udcp_del_nd", rtrn_udcp_del_nd);
190+
191+
m.def("pass_udmp_del_nd", pass_udmp_del_nd);
192+
m.def("pass_udcp_del_nd", pass_udcp_del_nd);
193+
133194
py::classh<uconsumer>(m, "uconsumer")
134195
.def(py::init<>())
135196
.def("valid", &uconsumer::valid)

tests/test_class_sh_basic.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,35 @@ def test_load_with_rtrn_f(pass_f, rtrn_f, expected):
6565
assert pass_f(rtrn_f()) == expected
6666

6767

68+
@pytest.mark.parametrize(
69+
("pass_f", "rtrn_f", "regex_expected"),
70+
[
71+
(
72+
m.pass_udmp_del,
73+
m.rtrn_udmp_del,
74+
"pass_udmp_del:rtrn_udmp_del,udmp_deleter(_MvCtorTo)*_MvCtorTo",
75+
),
76+
(
77+
m.pass_udcp_del,
78+
m.rtrn_udcp_del,
79+
"pass_udcp_del:rtrn_udcp_del,udcp_deleter(_MvCtorTo)*_MvCtorTo",
80+
),
81+
(
82+
m.pass_udmp_del_nd,
83+
m.rtrn_udmp_del_nd,
84+
"pass_udmp_del_nd:rtrn_udmp_del_nd,udmp_deleter_nd(_MvCtorTo)*_MvCtorTo",
85+
),
86+
(
87+
m.pass_udcp_del_nd,
88+
m.rtrn_udcp_del_nd,
89+
"pass_udcp_del_nd:rtrn_udcp_del_nd,udcp_deleter_nd(_MvCtorTo)*_MvCtorTo",
90+
),
91+
],
92+
)
93+
def test_deleter_roundtrip(pass_f, rtrn_f, regex_expected):
94+
assert re.match(regex_expected, pass_f(rtrn_f()))
95+
96+
6897
@pytest.mark.parametrize(
6998
("pass_f", "rtrn_f", "expected"),
7099
[

0 commit comments

Comments
 (0)