diff --git a/include/pybind11/pybind11.h b/include/pybind11/pybind11.h index fea2c0a00..25824362d 100644 --- a/include/pybind11/pybind11.h +++ b/include/pybind11/pybind11.h @@ -906,11 +906,19 @@ void set_operator_new(type_record *r) { r->operator_new = &T::operator new; } template void set_operator_new(...) { } +template struct has_operator_delete : std::false_type { }; +template struct has_operator_delete(T::operator delete))>> + : std::true_type { }; +template struct has_operator_delete_size : std::false_type { }; +template struct has_operator_delete_size(T::operator delete))>> + : std::true_type { }; /// Call class-specific delete if it exists or global otherwise. Can also be an overload set. -template (T::operator delete))>> -void call_operator_delete(T *p) { T::operator delete(p); } +template ::value, int> = 0> +void call_operator_delete(T *p, size_t) { T::operator delete(p); } +template ::value && has_operator_delete_size::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(); else - detail::call_operator_delete(v_h.value_ptr()); + detail::call_operator_delete(v_h.value_ptr(), v_h.type->type_size); } static detail::function_record *get_function_record(handle h) { diff --git a/tests/test_class.cpp b/tests/test_class.cpp index f616ba771..8761f2650 100644 --- a/tests/test_class.cpp +++ b/tests/test_class.cpp @@ -184,6 +184,46 @@ TEST_SUBMODULE(class_, m) { auto def = new PyMethodDef{"f", f, METH_VARARGS, nullptr}; return py::reinterpret_steal(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_(m, "HasOpNewDel").def(py::init<>()); + py::class_(m, "HasOpNewDelSize").def(py::init<>()); + py::class_(m, "HasOpNewDelBoth").def(py::init<>()); + py::class_ aliased(m, "AliasedHasOpNewDelSize"); + aliased.def(py::init<>()); + aliased.attr("size_noalias") = py::int_(sizeof(AliasedHasOpNewDelSize)); + aliased.attr("size_alias") = py::int_(sizeof(PyAliasedHasOpNewDelSize)); } template class BreaksBase {}; diff --git a/tests/test_class.py b/tests/test_class.py index 611a2870e..9236400e6 100644 --- a/tests/test_class.py +++ b/tests/test_class.py @@ -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" + )