From 0d765f4a7c3c6e32f43b576ddcbe23021ab8586f Mon Sep 17 00:00:00 2001 From: Dean Moldovan Date: Tue, 21 Mar 2017 01:15:20 +0100 Subject: [PATCH] Support class-specific operator new and delete Fixes #754. --- include/pybind11/attr.h | 3 +++ include/pybind11/cast.h | 1 + include/pybind11/class_support.h | 2 +- include/pybind11/common.h | 7 +++++++ include/pybind11/pybind11.h | 17 ++++++++++++++++- tests/test_eigen.cpp | 14 ++++++++++++++ tests/test_eigen.py | 10 ++++++++++ 7 files changed, 52 insertions(+), 2 deletions(-) diff --git a/include/pybind11/attr.h b/include/pybind11/attr.h index 450a55d52..e38a1a32d 100644 --- a/include/pybind11/attr.h +++ b/include/pybind11/attr.h @@ -174,6 +174,9 @@ struct type_record { /// How large is pybind11::instance? size_t instance_size = 0; + /// The global operator new can be overridden with a class-specific variant + void *(*operator_new)(size_t) = ::operator new; + /// Function pointer to class_<..>::init_holder void (*init_holder)(PyObject *, const void *) = nullptr; diff --git a/include/pybind11/cast.h b/include/pybind11/cast.h index 8684d7cae..fe19075e4 100644 --- a/include/pybind11/cast.h +++ b/include/pybind11/cast.h @@ -25,6 +25,7 @@ inline PyTypeObject *make_default_metaclass(); struct type_info { PyTypeObject *type; size_t type_size; + void *(*operator_new)(size_t); void (*init_holder)(PyObject *, const void *); void (*dealloc)(PyObject *); std::vector implicit_conversions; diff --git a/include/pybind11/class_support.h b/include/pybind11/class_support.h index 56abbf818..992703ff3 100644 --- a/include/pybind11/class_support.h +++ b/include/pybind11/class_support.h @@ -183,7 +183,7 @@ extern "C" inline PyObject *pybind11_object_new(PyTypeObject *type, PyObject *, PyObject *self = type->tp_alloc(type, 0); auto instance = (instance_essentials *) self; auto tinfo = get_type_info(type); - instance->value = ::operator new(tinfo->type_size); + instance->value = tinfo->operator_new(tinfo->type_size); instance->owned = true; instance->holder_constructed = false; get_internals().registered_instances.emplace(instance->value, self); diff --git a/include/pybind11/common.h b/include/pybind11/common.h index 9da1ae9fe..fba25c9f0 100644 --- a/include/pybind11/common.h +++ b/include/pybind11/common.h @@ -427,6 +427,13 @@ template using make_index_sequence = typename make_index_sequence_impl template using bool_constant = std::integral_constant; template struct negation : bool_constant { }; +#ifdef __cpp_lib_void_t +using std::void_t; +#else +template struct void_t_impl { using type = void; }; +template using void_t = typename void_t_impl::type; +#endif + /// Compile-time all/any/none of that check the boolean value of all template types #ifdef __cpp_fold_expressions template using all_of = bool_constant<(Ts::value && ...)>; diff --git a/include/pybind11/pybind11.h b/include/pybind11/pybind11.h index 0c4385052..42a19acb9 100644 --- a/include/pybind11/pybind11.h +++ b/include/pybind11/pybind11.h @@ -803,6 +803,7 @@ protected: auto *tinfo = new detail::type_info(); tinfo->type = (PyTypeObject *) m_ptr; tinfo->type_size = rec.type_size; + tinfo->operator_new = rec.operator_new; tinfo->init_holder = rec.init_holder; tinfo->dealloc = rec.dealloc; @@ -860,6 +861,18 @@ protected: } }; +/// Set the pointer to operator new if it exists. The cast is needed because it can be overloaded. +template (T::operator new))>> +void set_operator_new(type_record *r) { r->operator_new = &T::operator new; } + +template void set_operator_new(...) { } + +/// 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); } + +inline void call_operator_delete(void *p) { ::operator delete(p); } + NAMESPACE_END(detail) template @@ -905,6 +918,8 @@ public: record.dealloc = dealloc; record.default_holder = std::is_same>::value; + set_operator_new(&record); + /* Register base classes specified via template arguments to class_, if any */ bool unused[] = { (add_base(record), false)..., false }; (void) unused; @@ -1125,7 +1140,7 @@ private: if (inst->holder_constructed) inst->holder.~holder_type(); else if (inst->owned) - ::operator delete(inst->value); + detail::call_operator_delete(inst->value); } static detail::function_record *get_function_record(handle h) { diff --git a/tests/test_eigen.cpp b/tests/test_eigen.cpp index eeaceac1e..f2ec8fd2e 100644 --- a/tests/test_eigen.cpp +++ b/tests/test_eigen.cpp @@ -60,6 +60,15 @@ template Eigen::MatrixXd adjust_matrix(MatrixArgType m) return ret; } +struct CustomOperatorNew { + CustomOperatorNew() = default; + + Eigen::Matrix4d a = Eigen::Matrix4d::Zero(); + Eigen::Matrix4d b = Eigen::Matrix4d::Identity(); + + EIGEN_MAKE_ALIGNED_OPERATOR_NEW; +}; + test_initializer eigen([](py::module &m) { typedef Eigen::Matrix FixedMatrixR; typedef Eigen::Matrix FixedMatrixC; @@ -277,4 +286,9 @@ test_initializer eigen([](py::module &m) { // requiring a copy!) because the stride value can be safely ignored on a size-1 dimension. m.def("iss738_f1", &adjust_matrix &>, py::arg().noconvert()); m.def("iss738_f2", &adjust_matrix> &>, py::arg().noconvert()); + + py::class_(m, "CustomOperatorNew") + .def(py::init<>()) + .def_readonly("a", &CustomOperatorNew::a) + .def_readonly("b", &CustomOperatorNew::b); }); diff --git a/tests/test_eigen.py b/tests/test_eigen.py index f14d62255..df08edf3d 100644 --- a/tests/test_eigen.py +++ b/tests/test_eigen.py @@ -614,3 +614,13 @@ def test_issue738(): assert np.all(iss738_f2(np.array([[1., 2, 3]])) == np.array([[1., 102, 203]])) assert np.all(iss738_f2(np.array([[1.], [2], [3]])) == np.array([[1.], [12], [23]])) + + +def test_custom_operator_new(): + """Using Eigen types as member variables requires a class-specific + operator new with proper alignment""" + from pybind11_tests import CustomOperatorNew + + o = CustomOperatorNew() + np.testing.assert_allclose(o.a, 0.0) + np.testing.assert_allclose(o.b.diagonal(), 1.0)