Skip to content

Commit a03408c

Browse files
authored
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.
1 parent 7c0e2c2 commit a03408c

File tree

3 files changed

+104
-4
lines changed

3 files changed

+104
-4
lines changed

include/pybind11/pybind11.h

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -906,11 +906,19 @@ void set_operator_new(type_record *r) { r->operator_new = &T::operator new; }
906906

907907
template <typename> void set_operator_new(...) { }
908908

909+
template <typename T, typename SFINAE = void> struct has_operator_delete : std::false_type { };
910+
template <typename T> struct has_operator_delete<T, void_t<decltype(static_cast<void (*)(void *)>(T::operator delete))>>
911+
: std::true_type { };
912+
template <typename T, typename SFINAE = void> struct has_operator_delete_size : std::false_type { };
913+
template <typename T> struct has_operator_delete_size<T, void_t<decltype(static_cast<void (*)(void *, size_t)>(T::operator delete))>>
914+
: std::true_type { };
909915
/// Call class-specific delete if it exists or global otherwise. Can also be an overload set.
910-
template <typename T, typename = void_t<decltype(static_cast<void (*)(void *)>(T::operator delete))>>
911-
void call_operator_delete(T *p) { T::operator delete(p); }
916+
template <typename T, enable_if_t<has_operator_delete<T>::value, int> = 0>
917+
void call_operator_delete(T *p, size_t) { T::operator delete(p); }
918+
template <typename T, enable_if_t<!has_operator_delete<T>::value && has_operator_delete_size<T>::value, int> = 0>
919+
void call_operator_delete(T *p, size_t s) { T::operator delete(p, s); }
912920

913-
inline void call_operator_delete(void *p) { ::operator delete(p); }
921+
inline void call_operator_delete(void *p, size_t) { ::operator delete(p); }
914922

915923
NAMESPACE_END(detail)
916924

@@ -1212,7 +1220,7 @@ class class_ : public detail::generic_type {
12121220
if (v_h.holder_constructed())
12131221
v_h.holder<holder_type>().~holder_type();
12141222
else
1215-
detail::call_operator_delete(v_h.value_ptr<type>());
1223+
detail::call_operator_delete(v_h.value_ptr<type>(), v_h.type->type_size);
12161224
}
12171225

12181226
static detail::function_record *get_function_record(handle h) {

tests/test_class.cpp

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,46 @@ TEST_SUBMODULE(class_, m) {
184184
auto def = new PyMethodDef{"f", f, METH_VARARGS, nullptr};
185185
return py::reinterpret_steal<py::object>(PyCFunction_NewEx(def, nullptr, m.ptr()));
186186
}());
187+
188+
// test_operator_new_delete
189+
struct HasOpNewDel {
190+
std::uint64_t i;
191+
static void *operator new(size_t s) { py::print("A new", s); return ::operator new(s); }
192+
static void *operator new(size_t s, void *ptr) { py::print("A placement-new", s); return ptr; }
193+
static void operator delete(void *p) { py::print("A delete"); return ::operator delete(p); }
194+
};
195+
struct HasOpNewDelSize {
196+
std::uint32_t i;
197+
static void *operator new(size_t s) { py::print("B new", s); return ::operator new(s); }
198+
static void *operator new(size_t s, void *ptr) { py::print("B placement-new", s); return ptr; }
199+
static void operator delete(void *p, size_t s) { py::print("B delete", s); return ::operator delete(p); }
200+
};
201+
struct AliasedHasOpNewDelSize {
202+
std::uint64_t i;
203+
static void *operator new(size_t s) { py::print("C new", s); return ::operator new(s); }
204+
static void *operator new(size_t s, void *ptr) { py::print("C placement-new", s); return ptr; }
205+
static void operator delete(void *p, size_t s) { py::print("C delete", s); return ::operator delete(p); }
206+
virtual ~AliasedHasOpNewDelSize() = default;
207+
};
208+
struct PyAliasedHasOpNewDelSize : AliasedHasOpNewDelSize {
209+
PyAliasedHasOpNewDelSize() = default;
210+
PyAliasedHasOpNewDelSize(int) { }
211+
std::uint64_t j;
212+
};
213+
struct HasOpNewDelBoth {
214+
std::uint32_t i[8];
215+
static void *operator new(size_t s) { py::print("D new", s); return ::operator new(s); }
216+
static void *operator new(size_t s, void *ptr) { py::print("D placement-new", s); return ptr; }
217+
static void operator delete(void *p) { py::print("D delete"); return ::operator delete(p); }
218+
static void operator delete(void *p, size_t s) { py::print("D wrong delete", s); return ::operator delete(p); }
219+
};
220+
py::class_<HasOpNewDel>(m, "HasOpNewDel").def(py::init<>());
221+
py::class_<HasOpNewDelSize>(m, "HasOpNewDelSize").def(py::init<>());
222+
py::class_<HasOpNewDelBoth>(m, "HasOpNewDelBoth").def(py::init<>());
223+
py::class_<AliasedHasOpNewDelSize, PyAliasedHasOpNewDelSize> aliased(m, "AliasedHasOpNewDelSize");
224+
aliased.def(py::init<>());
225+
aliased.attr("size_noalias") = py::int_(sizeof(AliasedHasOpNewDelSize));
226+
aliased.attr("size_alias") = py::int_(sizeof(PyAliasedHasOpNewDelSize));
187227
}
188228

189229
template <int N> class BreaksBase {};

tests/test_class.py

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,3 +127,55 @@ def test_implicit_conversion_life_support():
127127
assert m.implicitly_convert_variable(UserType(5)) == 5
128128

129129
assert "outside a bound function" in m.implicitly_convert_variable_fail(UserType(5))
130+
131+
132+
def test_operator_new_delete(capture):
133+
"""Tests that class-specific operator new/delete functions are invoked"""
134+
135+
class SubAliased(m.AliasedHasOpNewDelSize):
136+
pass
137+
138+
with capture:
139+
a = m.HasOpNewDel()
140+
b = m.HasOpNewDelSize()
141+
d = m.HasOpNewDelBoth()
142+
assert capture == """
143+
A new 8
144+
A placement-new 8
145+
B new 4
146+
B placement-new 4
147+
D new 32
148+
D placement-new 32
149+
"""
150+
sz_alias = str(m.AliasedHasOpNewDelSize.size_alias)
151+
sz_noalias = str(m.AliasedHasOpNewDelSize.size_noalias)
152+
with capture:
153+
c = m.AliasedHasOpNewDelSize()
154+
c2 = SubAliased()
155+
assert capture == (
156+
"C new " + sz_alias + "\nC placement-new " + sz_noalias + "\n" +
157+
"C new " + sz_alias + "\nC placement-new " + sz_alias + "\n"
158+
)
159+
160+
with capture:
161+
del a
162+
ConstructorStats.detail_reg_inst()
163+
del b
164+
ConstructorStats.detail_reg_inst()
165+
del d
166+
ConstructorStats.detail_reg_inst()
167+
assert capture == """
168+
A delete
169+
B delete 4
170+
D delete
171+
"""
172+
173+
with capture:
174+
del c
175+
ConstructorStats.detail_reg_inst()
176+
del c2
177+
ConstructorStats.detail_reg_inst()
178+
assert capture == (
179+
"C delete " + sz_noalias + "\n" +
180+
"C delete " + sz_alias + "\n"
181+
)

0 commit comments

Comments
 (0)