Add support custom sized operator deletes (#952)

If a class doesn't provide a `T::operator delete(void *)` but does have
a `T::operator delete(void *, size_t)` the latter is invoked by a
`delete someT`.  Pybind currently only look for and call the former;
this commit adds detection and calling of the latter when the former
doesn't exist.
This commit is contained in:
Jason Rhinelander 2017-07-23 00:32:58 -04:00 committed by GitHub
parent 7c0e2c247b
commit a03408c839
3 changed files with 104 additions and 4 deletions

View File

@ -906,11 +906,19 @@ void set_operator_new(type_record *r) { r->operator_new = &T::operator new; }
template <typename> void set_operator_new(...) { }
template <typename T, typename SFINAE = void> struct has_operator_delete : std::false_type { };
template <typename T> struct has_operator_delete<T, void_t<decltype(static_cast<void (*)(void *)>(T::operator delete))>>
: std::true_type { };
template <typename T, typename SFINAE = void> struct has_operator_delete_size : std::false_type { };
template <typename T> struct has_operator_delete_size<T, void_t<decltype(static_cast<void (*)(void *, size_t)>(T::operator delete))>>
: std::true_type { };
/// Call class-specific delete if it exists or global otherwise. Can also be an overload set.
template <typename T, typename = void_t<decltype(static_cast<void (*)(void *)>(T::operator delete))>>
void call_operator_delete(T *p) { T::operator delete(p); }
template <typename T, enable_if_t<has_operator_delete<T>::value, int> = 0>
void call_operator_delete(T *p, size_t) { T::operator delete(p); }
template <typename T, enable_if_t<!has_operator_delete<T>::value && has_operator_delete_size<T>::value, int> = 0>
void call_operator_delete(T *p, size_t s) { T::operator delete(p, s); }
inline void call_operator_delete(void *p) { ::operator delete(p); }
inline void call_operator_delete(void *p, size_t) { ::operator delete(p); }
NAMESPACE_END(detail)
@ -1212,7 +1220,7 @@ private:
if (v_h.holder_constructed())
v_h.holder<holder_type>().~holder_type();
else
detail::call_operator_delete(v_h.value_ptr<type>());
detail::call_operator_delete(v_h.value_ptr<type>(), v_h.type->type_size);
}
static detail::function_record *get_function_record(handle h) {

View File

@ -184,6 +184,46 @@ TEST_SUBMODULE(class_, m) {
auto def = new PyMethodDef{"f", f, METH_VARARGS, nullptr};
return py::reinterpret_steal<py::object>(PyCFunction_NewEx(def, nullptr, m.ptr()));
}());
// test_operator_new_delete
struct HasOpNewDel {
std::uint64_t i;
static void *operator new(size_t s) { py::print("A new", s); return ::operator new(s); }
static void *operator new(size_t s, void *ptr) { py::print("A placement-new", s); return ptr; }
static void operator delete(void *p) { py::print("A delete"); return ::operator delete(p); }
};
struct HasOpNewDelSize {
std::uint32_t i;
static void *operator new(size_t s) { py::print("B new", s); return ::operator new(s); }
static void *operator new(size_t s, void *ptr) { py::print("B placement-new", s); return ptr; }
static void operator delete(void *p, size_t s) { py::print("B delete", s); return ::operator delete(p); }
};
struct AliasedHasOpNewDelSize {
std::uint64_t i;
static void *operator new(size_t s) { py::print("C new", s); return ::operator new(s); }
static void *operator new(size_t s, void *ptr) { py::print("C placement-new", s); return ptr; }
static void operator delete(void *p, size_t s) { py::print("C delete", s); return ::operator delete(p); }
virtual ~AliasedHasOpNewDelSize() = default;
};
struct PyAliasedHasOpNewDelSize : AliasedHasOpNewDelSize {
PyAliasedHasOpNewDelSize() = default;
PyAliasedHasOpNewDelSize(int) { }
std::uint64_t j;
};
struct HasOpNewDelBoth {
std::uint32_t i[8];
static void *operator new(size_t s) { py::print("D new", s); return ::operator new(s); }
static void *operator new(size_t s, void *ptr) { py::print("D placement-new", s); return ptr; }
static void operator delete(void *p) { py::print("D delete"); return ::operator delete(p); }
static void operator delete(void *p, size_t s) { py::print("D wrong delete", s); return ::operator delete(p); }
};
py::class_<HasOpNewDel>(m, "HasOpNewDel").def(py::init<>());
py::class_<HasOpNewDelSize>(m, "HasOpNewDelSize").def(py::init<>());
py::class_<HasOpNewDelBoth>(m, "HasOpNewDelBoth").def(py::init<>());
py::class_<AliasedHasOpNewDelSize, PyAliasedHasOpNewDelSize> aliased(m, "AliasedHasOpNewDelSize");
aliased.def(py::init<>());
aliased.attr("size_noalias") = py::int_(sizeof(AliasedHasOpNewDelSize));
aliased.attr("size_alias") = py::int_(sizeof(PyAliasedHasOpNewDelSize));
}
template <int N> class BreaksBase {};

View File

@ -127,3 +127,55 @@ def test_implicit_conversion_life_support():
assert m.implicitly_convert_variable(UserType(5)) == 5
assert "outside a bound function" in m.implicitly_convert_variable_fail(UserType(5))
def test_operator_new_delete(capture):
"""Tests that class-specific operator new/delete functions are invoked"""
class SubAliased(m.AliasedHasOpNewDelSize):
pass
with capture:
a = m.HasOpNewDel()
b = m.HasOpNewDelSize()
d = m.HasOpNewDelBoth()
assert capture == """
A new 8
A placement-new 8
B new 4
B placement-new 4
D new 32
D placement-new 32
"""
sz_alias = str(m.AliasedHasOpNewDelSize.size_alias)
sz_noalias = str(m.AliasedHasOpNewDelSize.size_noalias)
with capture:
c = m.AliasedHasOpNewDelSize()
c2 = SubAliased()
assert capture == (
"C new " + sz_alias + "\nC placement-new " + sz_noalias + "\n" +
"C new " + sz_alias + "\nC placement-new " + sz_alias + "\n"
)
with capture:
del a
ConstructorStats.detail_reg_inst()
del b
ConstructorStats.detail_reg_inst()
del d
ConstructorStats.detail_reg_inst()
assert capture == """
A delete
B delete 4
D delete
"""
with capture:
del c
ConstructorStats.detail_reg_inst()
del c2
ConstructorStats.detail_reg_inst()
assert capture == (
"C delete " + sz_noalias + "\n" +
"C delete " + sz_alias + "\n"
)