mirror of
https://github.com/pybind/pybind11.git
synced 2024-11-11 08:03:55 +00:00
[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.
This commit is contained in:
parent
e02fe001cd
commit
e5ce9631b1
@ -464,6 +464,27 @@ struct shared_ptr_trampoline_self_life_support {
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T,
|
||||
typename D,
|
||||
typename std::enable_if<std::is_default_constructible<D>::value, int>::type = 0>
|
||||
inline std::unique_ptr<T, D> unique_with_deleter(T *raw_ptr, std::unique_ptr<D> &&deleter) {
|
||||
if (deleter == nullptr) {
|
||||
return std::unique_ptr<T, D>(raw_ptr);
|
||||
}
|
||||
return std::unique_ptr<T, D>(raw_ptr, std::move(*deleter));
|
||||
}
|
||||
|
||||
template <typename T,
|
||||
typename D,
|
||||
typename std::enable_if<!std::is_default_constructible<D>::value, int>::type = 0>
|
||||
inline std::unique_ptr<T, D> unique_with_deleter(T *raw_ptr, std::unique_ptr<D> &&deleter) {
|
||||
if (deleter == nullptr) {
|
||||
pybind11_fail("smart_holder_type_casters: deleter is not default constructible and no"
|
||||
" instance available to return.");
|
||||
}
|
||||
return std::unique_ptr<T, D>(raw_ptr, std::move(*deleter));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
struct smart_holder_type_caster_load {
|
||||
using holder_type = pybindit::memory::smart_holder;
|
||||
@ -589,7 +610,7 @@ struct smart_holder_type_caster_load {
|
||||
" std::unique_ptr.");
|
||||
}
|
||||
if (!have_holder()) {
|
||||
return nullptr;
|
||||
return unique_with_deleter<T, D>(nullptr, std::unique_ptr<D>());
|
||||
}
|
||||
throw_if_uninitialized_or_disowned_holder();
|
||||
throw_if_instance_is_currently_owned_by_shared_ptr();
|
||||
@ -616,13 +637,30 @@ struct smart_holder_type_caster_load {
|
||||
"instance cannot safely be transferred to C++.");
|
||||
}
|
||||
|
||||
// Temporary variable to store the extracted deleter in.
|
||||
std::unique_ptr<D> extracted_deleter;
|
||||
|
||||
auto *gd = std::get_deleter<pybindit::memory::guarded_delete>(holder().vptr);
|
||||
if (gd && gd->use_del_fun) { // Note the ensure_compatible_rtti_uqp_del<T, D>() call above.
|
||||
// In smart_holder_poc, a custom deleter is always stored in a guarded delete.
|
||||
// The guarded delete's std::function<void(void*)> actually points at the
|
||||
// custom_deleter type, so we can verify it is of the custom deleter type and
|
||||
// finally extract its deleter.
|
||||
using custom_deleter_D = pybindit::memory::custom_deleter<T, D>;
|
||||
const auto &custom_deleter_ptr = gd->del_fun.template target<custom_deleter_D>();
|
||||
assert(custom_deleter_ptr != nullptr);
|
||||
// Now that we have confirmed the type of the deleter matches the desired return
|
||||
// value we can extract the function.
|
||||
extracted_deleter = std::unique_ptr<D>(new D(std::move(custom_deleter_ptr->deleter)));
|
||||
}
|
||||
|
||||
// Critical transfer-of-ownership section. This must stay together.
|
||||
if (self_life_support != nullptr) {
|
||||
holder().disown();
|
||||
} else {
|
||||
holder().release_ownership();
|
||||
}
|
||||
auto result = std::unique_ptr<T, D>(raw_type_ptr);
|
||||
auto result = unique_with_deleter<T, D>(raw_type_ptr, std::move(extracted_deleter));
|
||||
if (self_life_support != nullptr) {
|
||||
self_life_support->activate_life_support(load_impl.loaded_v_h);
|
||||
} else {
|
||||
@ -1056,7 +1094,8 @@ struct smart_holder_type_caster<std::unique_ptr<T const, D>>
|
||||
static handle
|
||||
cast(std::unique_ptr<T const, D> &&src, return_value_policy policy, handle parent) {
|
||||
return smart_holder_type_caster<std::unique_ptr<T, D>>::cast(
|
||||
std::unique_ptr<T, D>(const_cast<T *>(src.release())), // Const2Mutbl
|
||||
std::unique_ptr<T, D>(const_cast<T *>(src.release()),
|
||||
std::move(src.get_deleter())), // Const2Mutbl
|
||||
policy,
|
||||
parent);
|
||||
}
|
||||
|
@ -28,6 +28,43 @@ struct uconsumer { // unique_ptr consumer
|
||||
const std::unique_ptr<atyp> &rtrn_cref() const { return held; }
|
||||
};
|
||||
|
||||
/// Custom deleter that is default constructible.
|
||||
struct custom_deleter {
|
||||
std::string trace_txt;
|
||||
|
||||
custom_deleter() = default;
|
||||
explicit custom_deleter(const std::string &trace_txt_) : trace_txt(trace_txt_) {}
|
||||
|
||||
custom_deleter(const custom_deleter &other) { trace_txt = other.trace_txt + "_CpCtor"; }
|
||||
|
||||
custom_deleter &operator=(const custom_deleter &rhs) {
|
||||
trace_txt = rhs.trace_txt + "_CpLhs";
|
||||
return *this;
|
||||
}
|
||||
|
||||
custom_deleter(custom_deleter &&other) noexcept {
|
||||
trace_txt = other.trace_txt + "_MvCtorTo";
|
||||
other.trace_txt += "_MvCtorFrom";
|
||||
}
|
||||
|
||||
custom_deleter &operator=(custom_deleter &&rhs) noexcept {
|
||||
trace_txt = rhs.trace_txt + "_MvLhs";
|
||||
rhs.trace_txt += "_MvRhs";
|
||||
return *this;
|
||||
}
|
||||
|
||||
void operator()(atyp *p) const { std::default_delete<atyp>()(p); }
|
||||
void operator()(const atyp *p) const { std::default_delete<const atyp>()(p); }
|
||||
};
|
||||
static_assert(std::is_default_constructible<custom_deleter>::value, "");
|
||||
|
||||
/// Custom deleter that is not default constructible.
|
||||
struct custom_deleter_nd : custom_deleter {
|
||||
custom_deleter_nd() = delete;
|
||||
explicit custom_deleter_nd(const std::string &trace_txt_) : custom_deleter(trace_txt_) {}
|
||||
};
|
||||
static_assert(!std::is_default_constructible<custom_deleter_nd>::value, "");
|
||||
|
||||
// clang-format off
|
||||
|
||||
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
|
||||
std::string pass_udmp(std::unique_ptr<atyp, sddm> obj) { return "pass_udmp:" + obj->mtxt; }
|
||||
std::string pass_udcp(std::unique_ptr<atyp const, sddc> obj) { return "pass_udcp:" + obj->mtxt; }
|
||||
|
||||
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"}); }
|
||||
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"}); }
|
||||
|
||||
std::string pass_udmp_del(std::unique_ptr<atyp, custom_deleter> obj) { return "pass_udmp_del:" + obj->mtxt + "," + obj.get_deleter().trace_txt; }
|
||||
std::string pass_udcp_del(std::unique_ptr<atyp const, custom_deleter> obj) { return "pass_udcp_del:" + obj->mtxt + "," + obj.get_deleter().trace_txt; }
|
||||
|
||||
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"}); }
|
||||
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"}); }
|
||||
|
||||
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; }
|
||||
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; }
|
||||
|
||||
// clang-format on
|
||||
|
||||
// Helpers for testing.
|
||||
@ -130,6 +179,18 @@ TEST_SUBMODULE(class_sh_basic, m) {
|
||||
m.def("pass_udmp", pass_udmp);
|
||||
m.def("pass_udcp", pass_udcp);
|
||||
|
||||
m.def("rtrn_udmp_del", rtrn_udmp_del);
|
||||
m.def("rtrn_udcp_del", rtrn_udcp_del);
|
||||
|
||||
m.def("pass_udmp_del", pass_udmp_del);
|
||||
m.def("pass_udcp_del", pass_udcp_del);
|
||||
|
||||
m.def("rtrn_udmp_del_nd", rtrn_udmp_del_nd);
|
||||
m.def("rtrn_udcp_del_nd", rtrn_udcp_del_nd);
|
||||
|
||||
m.def("pass_udmp_del_nd", pass_udmp_del_nd);
|
||||
m.def("pass_udcp_del_nd", pass_udcp_del_nd);
|
||||
|
||||
py::classh<uconsumer>(m, "uconsumer")
|
||||
.def(py::init<>())
|
||||
.def("valid", &uconsumer::valid)
|
||||
|
@ -65,6 +65,35 @@ def test_load_with_rtrn_f(pass_f, rtrn_f, expected):
|
||||
assert pass_f(rtrn_f()) == expected
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("pass_f", "rtrn_f", "regex_expected"),
|
||||
[
|
||||
(
|
||||
m.pass_udmp_del,
|
||||
m.rtrn_udmp_del,
|
||||
"pass_udmp_del:rtrn_udmp_del,udmp_deleter(_MvCtorTo)*_MvCtorTo",
|
||||
),
|
||||
(
|
||||
m.pass_udcp_del,
|
||||
m.rtrn_udcp_del,
|
||||
"pass_udcp_del:rtrn_udcp_del,udcp_deleter(_MvCtorTo)*_MvCtorTo",
|
||||
),
|
||||
(
|
||||
m.pass_udmp_del_nd,
|
||||
m.rtrn_udmp_del_nd,
|
||||
"pass_udmp_del_nd:rtrn_udmp_del_nd,udmp_deleter_nd(_MvCtorTo)*_MvCtorTo",
|
||||
),
|
||||
(
|
||||
m.pass_udcp_del_nd,
|
||||
m.rtrn_udcp_del_nd,
|
||||
"pass_udcp_del_nd:rtrn_udcp_del_nd,udcp_deleter_nd(_MvCtorTo)*_MvCtorTo",
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_deleter_roundtrip(pass_f, rtrn_f, regex_expected):
|
||||
assert re.match(regex_expected, pass_f(rtrn_f()))
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("pass_f", "rtrn_f", "expected"),
|
||||
[
|
||||
|
Loading…
Reference in New Issue
Block a user