mirror of
https://github.com/pybind/pybind11.git
synced 2026-04-29 03:01:32 +00:00
Add static_asserts to enforce that py::smart_holder is combined with py::trampoline_self_life_support (#5633)
* Strictly enforce: trampoline must inherit from trampoline_self_life_support when used in combination with smart_holder
* Simplify test_class_sh_trampoline_basic.cpp,py (only one Abase is needed now)
* Replace obsolete sophisticated `throw value_error()` with a simple `assert()`
* Strictly enforce: trampoline should inherit from trampoline_self_life_support only if used in combination with smart_holder
* Resolve clang-tidy error
```
/__w/pybind11/pybind11/tests/test_class_sh_trampoline_basic.cpp:35:46: error: the parameter 'obj' is copied for each invocation but only used as a const reference; consider making it a const reference [performance-unnecessary-value-param,-warnings-as-errors]
35 | int AddInCppSharedPtr(std::shared_ptr<Abase> obj, int other_val) {
| ^
| const &
```
* Disable new static_assert if PYBIND11_RUN_TESTING_WITH_SMART_HOLDER_AS_DEFAULT_BUT_NEVER_USE_IN_PRODUCTION_PLEASE is defined.
This commit is contained in:
committed by
GitHub
parent
c7f3460f18
commit
c630e22c1c
@@ -82,15 +82,21 @@ helper class that is defined as follows:
|
|||||||
|
|
||||||
The ``py::trampoline_self_life_support`` base class is needed to ensure
|
The ``py::trampoline_self_life_support`` base class is needed to ensure
|
||||||
that a ``std::unique_ptr`` can safely be passed between Python and C++. To
|
that a ``std::unique_ptr`` can safely be passed between Python and C++. To
|
||||||
steer clear of notorious pitfalls (e.g. inheritance slicing), it is best
|
help you steer clear of notorious pitfalls (e.g. inheritance slicing),
|
||||||
practice to always use the base class, in combination with
|
pybind11 enforces that trampoline classes inherit from
|
||||||
|
``py::trampoline_self_life_support`` if used in in combination with
|
||||||
``py::smart_holder``.
|
``py::smart_holder``.
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
For completeness, the base class has no effect if a holder other than
|
For completeness, the base class has no effect if a holder other than
|
||||||
``py::smart_holder`` used, including the default ``std::unique_ptr<T>``.
|
``py::smart_holder`` used, including the default ``std::unique_ptr<T>``.
|
||||||
Please think twice, though, the pitfalls are very real, and the overhead
|
To avoid confusion, pybind11 will fail to compile bindings that combine
|
||||||
for using the safer ``py::smart_holder`` is very likely to be in the noise.
|
``py::trampoline_self_life_support`` with a holder other than
|
||||||
|
``py::smart_holder``.
|
||||||
|
|
||||||
|
Please think twice, though, before deciding to not use the safer
|
||||||
|
``py::smart_holder``. The pitfalls associated with avoiding it are very
|
||||||
|
real, and the overhead for using it is very likely in the noise.
|
||||||
|
|
||||||
The macro :c:macro:`PYBIND11_OVERRIDE_PURE` should be used for pure virtual
|
The macro :c:macro:`PYBIND11_OVERRIDE_PURE` should be used for pure virtual
|
||||||
functions, and :c:macro:`PYBIND11_OVERRIDE` should be used for functions which have
|
functions, and :c:macro:`PYBIND11_OVERRIDE` should be used for functions which have
|
||||||
|
|||||||
@@ -827,11 +827,8 @@ struct load_helper : value_and_holder_helper {
|
|||||||
|
|
||||||
auto *self_life_support
|
auto *self_life_support
|
||||||
= dynamic_raw_ptr_cast_if_possible<trampoline_self_life_support>(raw_type_ptr);
|
= dynamic_raw_ptr_cast_if_possible<trampoline_self_life_support>(raw_type_ptr);
|
||||||
if (self_life_support == nullptr && python_instance_is_alias) {
|
// This is enforced indirectly by a static_assert in the class_ implementation:
|
||||||
throw value_error("Alias class (also known as trampoline) does not inherit from "
|
assert(!python_instance_is_alias || self_life_support);
|
||||||
"py::trampoline_self_life_support, therefore the ownership of this "
|
|
||||||
"instance cannot safely be transferred to C++.");
|
|
||||||
}
|
|
||||||
|
|
||||||
std::unique_ptr<D> extracted_deleter = holder().template extract_deleter<T, D>(context);
|
std::unique_ptr<D> extracted_deleter = holder().template extract_deleter<T, D>(context);
|
||||||
|
|
||||||
|
|||||||
@@ -20,6 +20,7 @@
|
|||||||
#include "gil.h"
|
#include "gil.h"
|
||||||
#include "gil_safe_call_once.h"
|
#include "gil_safe_call_once.h"
|
||||||
#include "options.h"
|
#include "options.h"
|
||||||
|
#include "trampoline_self_life_support.h"
|
||||||
#include "typing.h"
|
#include "typing.h"
|
||||||
|
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
@@ -1982,7 +1983,21 @@ public:
|
|||||||
"Unknown/invalid class_ template parameters provided");
|
"Unknown/invalid class_ template parameters provided");
|
||||||
|
|
||||||
static_assert(!has_alias || std::is_polymorphic<type>::value,
|
static_assert(!has_alias || std::is_polymorphic<type>::value,
|
||||||
"Cannot use an alias class with a non-polymorphic type");
|
"Cannot use an alias class (aka trampoline) with a non-polymorphic type");
|
||||||
|
|
||||||
|
#ifndef PYBIND11_RUN_TESTING_WITH_SMART_HOLDER_AS_DEFAULT_BUT_NEVER_USE_IN_PRODUCTION_PLEASE
|
||||||
|
static_assert(!has_alias || !detail::is_smart_holder<holder_type>::value
|
||||||
|
|| std::is_base_of<trampoline_self_life_support, type_alias>::value,
|
||||||
|
"Alias class (aka trampoline) must inherit from"
|
||||||
|
" pybind11::trampoline_self_life_support if used in combination with"
|
||||||
|
" pybind11::smart_holder");
|
||||||
|
#endif
|
||||||
|
static_assert(!has_alias || detail::is_smart_holder<holder_type>::value
|
||||||
|
|| !std::is_base_of<trampoline_self_life_support, type_alias>::value,
|
||||||
|
"pybind11::trampoline_self_life_support is a smart_holder feature, therefore"
|
||||||
|
" an alias class (aka trampoline) should inherit from"
|
||||||
|
" pybind11::trampoline_self_life_support only if used in combination with"
|
||||||
|
" pybind11::smart_holder");
|
||||||
|
|
||||||
PYBIND11_OBJECT(class_, generic_type, PyType_Check)
|
PYBIND11_OBJECT(class_, generic_type, PyType_Check)
|
||||||
|
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ struct with_alias {
|
|||||||
with_alias &operator=(const with_alias &) = default;
|
with_alias &operator=(const with_alias &) = default;
|
||||||
with_alias &operator=(with_alias &&) = default;
|
with_alias &operator=(with_alias &&) = default;
|
||||||
};
|
};
|
||||||
struct with_alias_alias : with_alias {};
|
struct with_alias_alias : with_alias, py::trampoline_self_life_support {};
|
||||||
struct sddwaa : std::default_delete<with_alias_alias> {};
|
struct sddwaa : std::default_delete<with_alias_alias> {};
|
||||||
|
|
||||||
} // namespace class_sh_factory_constructors
|
} // namespace class_sh_factory_constructors
|
||||||
|
|||||||
@@ -5,7 +5,6 @@
|
|||||||
namespace pybind11_tests {
|
namespace pybind11_tests {
|
||||||
namespace class_sh_trampoline_basic {
|
namespace class_sh_trampoline_basic {
|
||||||
|
|
||||||
template <int SerNo> // Using int as a trick to easily generate a series of types.
|
|
||||||
struct Abase {
|
struct Abase {
|
||||||
int val = 0;
|
int val = 0;
|
||||||
virtual ~Abase() = default;
|
virtual ~Abase() = default;
|
||||||
@@ -20,63 +19,39 @@ struct Abase {
|
|||||||
Abase &operator=(Abase &&) noexcept = default;
|
Abase &operator=(Abase &&) noexcept = default;
|
||||||
};
|
};
|
||||||
|
|
||||||
template <int SerNo>
|
struct AbaseAlias : Abase, py::trampoline_self_life_support {
|
||||||
struct AbaseAlias : Abase<SerNo> {
|
using Abase::Abase;
|
||||||
using Abase<SerNo>::Abase;
|
|
||||||
|
|
||||||
int Add(int other_val) const override {
|
int Add(int other_val) const override {
|
||||||
PYBIND11_OVERRIDE_PURE(int, /* Return type */
|
PYBIND11_OVERRIDE_PURE(int, /* Return type */
|
||||||
Abase<SerNo>, /* Parent class */
|
Abase, /* Parent class */
|
||||||
Add, /* Name of function in C++ (must match Python name) */
|
Add, /* Name of function in C++ (must match Python name) */
|
||||||
other_val);
|
other_val);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
template <>
|
int AddInCppRawPtr(const Abase *obj, int other_val) { return obj->Add(other_val) * 10 + 7; }
|
||||||
struct AbaseAlias<1> : Abase<1>, py::trampoline_self_life_support {
|
|
||||||
using Abase<1>::Abase;
|
|
||||||
|
|
||||||
int Add(int other_val) const override {
|
int AddInCppSharedPtr(const std::shared_ptr<Abase> &obj, int other_val) {
|
||||||
PYBIND11_OVERRIDE_PURE(int, /* Return type */
|
|
||||||
Abase<1>, /* Parent class */
|
|
||||||
Add, /* Name of function in C++ (must match Python name) */
|
|
||||||
other_val);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
template <int SerNo>
|
|
||||||
int AddInCppRawPtr(const Abase<SerNo> *obj, int other_val) {
|
|
||||||
return obj->Add(other_val) * 10 + 7;
|
|
||||||
}
|
|
||||||
|
|
||||||
template <int SerNo>
|
|
||||||
int AddInCppSharedPtr(std::shared_ptr<Abase<SerNo>> obj, int other_val) {
|
|
||||||
return obj->Add(other_val) * 100 + 11;
|
return obj->Add(other_val) * 100 + 11;
|
||||||
}
|
}
|
||||||
|
|
||||||
template <int SerNo>
|
int AddInCppUniquePtr(std::unique_ptr<Abase> obj, int other_val) {
|
||||||
int AddInCppUniquePtr(std::unique_ptr<Abase<SerNo>> obj, int other_val) {
|
|
||||||
return obj->Add(other_val) * 100 + 13;
|
return obj->Add(other_val) * 100 + 13;
|
||||||
}
|
}
|
||||||
|
|
||||||
template <int SerNo>
|
|
||||||
void wrap(py::module_ m, const char *py_class_name) {
|
|
||||||
py::classh<Abase<SerNo>, AbaseAlias<SerNo>>(m, py_class_name)
|
|
||||||
.def(py::init<int>(), py::arg("val"))
|
|
||||||
.def("Get", &Abase<SerNo>::Get)
|
|
||||||
.def("Add", &Abase<SerNo>::Add, py::arg("other_val"));
|
|
||||||
|
|
||||||
m.def("AddInCppRawPtr", AddInCppRawPtr<SerNo>, py::arg("obj"), py::arg("other_val"));
|
|
||||||
m.def("AddInCppSharedPtr", AddInCppSharedPtr<SerNo>, py::arg("obj"), py::arg("other_val"));
|
|
||||||
m.def("AddInCppUniquePtr", AddInCppUniquePtr<SerNo>, py::arg("obj"), py::arg("other_val"));
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace class_sh_trampoline_basic
|
} // namespace class_sh_trampoline_basic
|
||||||
} // namespace pybind11_tests
|
} // namespace pybind11_tests
|
||||||
|
|
||||||
using namespace pybind11_tests::class_sh_trampoline_basic;
|
using namespace pybind11_tests::class_sh_trampoline_basic;
|
||||||
|
|
||||||
TEST_SUBMODULE(class_sh_trampoline_basic, m) {
|
TEST_SUBMODULE(class_sh_trampoline_basic, m) {
|
||||||
wrap<0>(m, "Abase0");
|
py::classh<Abase, AbaseAlias>(m, "Abase")
|
||||||
wrap<1>(m, "Abase1");
|
.def(py::init<int>(), py::arg("val"))
|
||||||
|
.def("Get", &Abase::Get)
|
||||||
|
.def("Add", &Abase::Add, py::arg("other_val"));
|
||||||
|
|
||||||
|
m.def("AddInCppRawPtr", AddInCppRawPtr, py::arg("obj"), py::arg("other_val"));
|
||||||
|
m.def("AddInCppSharedPtr", AddInCppSharedPtr, py::arg("obj"), py::arg("other_val"));
|
||||||
|
m.def("AddInCppUniquePtr", AddInCppUniquePtr, py::arg("obj"), py::arg("other_val"));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,9 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import pytest
|
|
||||||
|
|
||||||
from pybind11_tests import class_sh_trampoline_basic as m
|
from pybind11_tests import class_sh_trampoline_basic as m
|
||||||
|
|
||||||
|
|
||||||
class PyDrvd0(m.Abase0):
|
class PyDrvd(m.Abase):
|
||||||
def __init__(self, val):
|
def __init__(self, val):
|
||||||
super().__init__(val)
|
super().__init__(val)
|
||||||
|
|
||||||
@@ -13,47 +11,25 @@ class PyDrvd0(m.Abase0):
|
|||||||
return self.Get() * 100 + other_val
|
return self.Get() * 100 + other_val
|
||||||
|
|
||||||
|
|
||||||
class PyDrvd1(m.Abase1):
|
def test_drvd_add():
|
||||||
def __init__(self, val):
|
drvd = PyDrvd(74)
|
||||||
super().__init__(val)
|
|
||||||
|
|
||||||
def Add(self, other_val):
|
|
||||||
return self.Get() * 200 + other_val
|
|
||||||
|
|
||||||
|
|
||||||
def test_drvd0_add():
|
|
||||||
drvd = PyDrvd0(74)
|
|
||||||
assert drvd.Add(38) == (74 * 10 + 3) * 100 + 38
|
assert drvd.Add(38) == (74 * 10 + 3) * 100 + 38
|
||||||
|
|
||||||
|
|
||||||
def test_drvd0_add_in_cpp_raw_ptr():
|
def test_drvd_add_in_cpp_raw_ptr():
|
||||||
drvd = PyDrvd0(52)
|
drvd = PyDrvd(52)
|
||||||
assert m.AddInCppRawPtr(drvd, 27) == ((52 * 10 + 3) * 100 + 27) * 10 + 7
|
assert m.AddInCppRawPtr(drvd, 27) == ((52 * 10 + 3) * 100 + 27) * 10 + 7
|
||||||
|
|
||||||
|
|
||||||
def test_drvd0_add_in_cpp_shared_ptr():
|
def test_drvd_add_in_cpp_shared_ptr():
|
||||||
while True:
|
while True:
|
||||||
drvd = PyDrvd0(36)
|
drvd = PyDrvd(36)
|
||||||
assert m.AddInCppSharedPtr(drvd, 56) == ((36 * 10 + 3) * 100 + 56) * 100 + 11
|
assert m.AddInCppSharedPtr(drvd, 56) == ((36 * 10 + 3) * 100 + 56) * 100 + 11
|
||||||
return # Comment out for manual leak checking (use `top` command).
|
return # Comment out for manual leak checking (use `top` command).
|
||||||
|
|
||||||
|
|
||||||
def test_drvd0_add_in_cpp_unique_ptr():
|
def test_drvd_add_in_cpp_unique_ptr():
|
||||||
while True:
|
while True:
|
||||||
drvd = PyDrvd0(0)
|
drvd = PyDrvd(25)
|
||||||
with pytest.raises(ValueError) as exc_info:
|
assert m.AddInCppUniquePtr(drvd, 83) == ((25 * 10 + 3) * 100 + 83) * 100 + 13
|
||||||
m.AddInCppUniquePtr(drvd, 0)
|
|
||||||
assert (
|
|
||||||
str(exc_info.value)
|
|
||||||
== "Alias class (also known as trampoline) does not inherit from"
|
|
||||||
" py::trampoline_self_life_support, therefore the ownership of this"
|
|
||||||
" instance cannot safely be transferred to C++."
|
|
||||||
)
|
|
||||||
return # Comment out for manual leak checking (use `top` command).
|
|
||||||
|
|
||||||
|
|
||||||
def test_drvd1_add_in_cpp_unique_ptr():
|
|
||||||
while True:
|
|
||||||
drvd = PyDrvd1(25)
|
|
||||||
assert m.AddInCppUniquePtr(drvd, 83) == ((25 * 10 + 3) * 200 + 83) * 100 + 13
|
|
||||||
return # Comment out for manual leak checking (use `top` command).
|
return # Comment out for manual leak checking (use `top` command).
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ struct SpBase {
|
|||||||
|
|
||||||
std::shared_ptr<SpBase> pass_through_shd_ptr(const std::shared_ptr<SpBase> &obj) { return obj; }
|
std::shared_ptr<SpBase> pass_through_shd_ptr(const std::shared_ptr<SpBase> &obj) { return obj; }
|
||||||
|
|
||||||
struct PySpBase : SpBase {
|
struct PySpBase : SpBase, py::trampoline_self_life_support {
|
||||||
using SpBase::SpBase;
|
using SpBase::SpBase;
|
||||||
bool is_base_used() override { PYBIND11_OVERRIDE(bool, SpBase, is_base_used); }
|
bool is_base_used() override { PYBIND11_OVERRIDE(bool, SpBase, is_base_used); }
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user