mirror of
https://github.com/thelsing/knx.git
synced 2024-12-23 19:09:41 +01:00
174 lines
6.2 KiB
ReStructuredText
174 lines
6.2 KiB
ReStructuredText
Smart pointers
|
|
##############
|
|
|
|
std::unique_ptr
|
|
===============
|
|
|
|
Given a class ``Example`` with Python bindings, it's possible to return
|
|
instances wrapped in C++11 unique pointers, like so
|
|
|
|
.. code-block:: cpp
|
|
|
|
std::unique_ptr<Example> create_example() { return std::unique_ptr<Example>(new Example()); }
|
|
|
|
.. code-block:: cpp
|
|
|
|
m.def("create_example", &create_example);
|
|
|
|
In other words, there is nothing special that needs to be done. While returning
|
|
unique pointers in this way is allowed, it is *illegal* to use them as function
|
|
arguments. For instance, the following function signature cannot be processed
|
|
by pybind11.
|
|
|
|
.. code-block:: cpp
|
|
|
|
void do_something_with_example(std::unique_ptr<Example> ex) { ... }
|
|
|
|
The above signature would imply that Python needs to give up ownership of an
|
|
object that is passed to this function, which is generally not possible (for
|
|
instance, the object might be referenced elsewhere).
|
|
|
|
std::shared_ptr
|
|
===============
|
|
|
|
The binding generator for classes, :class:`class_`, can be passed a template
|
|
type that denotes a special *holder* type that is used to manage references to
|
|
the object. If no such holder type template argument is given, the default for
|
|
a type named ``Type`` is ``std::unique_ptr<Type>``, which means that the object
|
|
is deallocated when Python's reference count goes to zero.
|
|
|
|
It is possible to switch to other types of reference counting wrappers or smart
|
|
pointers, which is useful in codebases that rely on them. For instance, the
|
|
following snippet causes ``std::shared_ptr`` to be used instead.
|
|
|
|
.. code-block:: cpp
|
|
|
|
py::class_<Example, std::shared_ptr<Example> /* <- holder type */> obj(m, "Example");
|
|
|
|
Note that any particular class can only be associated with a single holder type.
|
|
|
|
One potential stumbling block when using holder types is that they need to be
|
|
applied consistently. Can you guess what's broken about the following binding
|
|
code?
|
|
|
|
.. code-block:: cpp
|
|
|
|
class Child { };
|
|
|
|
class Parent {
|
|
public:
|
|
Parent() : child(std::make_shared<Child>()) { }
|
|
Child *get_child() { return child.get(); } /* Hint: ** DON'T DO THIS ** */
|
|
private:
|
|
std::shared_ptr<Child> child;
|
|
};
|
|
|
|
PYBIND11_MODULE(example, m) {
|
|
py::class_<Child, std::shared_ptr<Child>>(m, "Child");
|
|
|
|
py::class_<Parent, std::shared_ptr<Parent>>(m, "Parent")
|
|
.def(py::init<>())
|
|
.def("get_child", &Parent::get_child);
|
|
}
|
|
|
|
The following Python code will cause undefined behavior (and likely a
|
|
segmentation fault).
|
|
|
|
.. code-block:: python
|
|
|
|
from example import Parent
|
|
print(Parent().get_child())
|
|
|
|
The problem is that ``Parent::get_child()`` returns a pointer to an instance of
|
|
``Child``, but the fact that this instance is already managed by
|
|
``std::shared_ptr<...>`` is lost when passing raw pointers. In this case,
|
|
pybind11 will create a second independent ``std::shared_ptr<...>`` that also
|
|
claims ownership of the pointer. In the end, the object will be freed **twice**
|
|
since these shared pointers have no way of knowing about each other.
|
|
|
|
There are two ways to resolve this issue:
|
|
|
|
1. For types that are managed by a smart pointer class, never use raw pointers
|
|
in function arguments or return values. In other words: always consistently
|
|
wrap pointers into their designated holder types (such as
|
|
``std::shared_ptr<...>``). In this case, the signature of ``get_child()``
|
|
should be modified as follows:
|
|
|
|
.. code-block:: cpp
|
|
|
|
std::shared_ptr<Child> get_child() { return child; }
|
|
|
|
2. Adjust the definition of ``Child`` by specifying
|
|
``std::enable_shared_from_this<T>`` (see cppreference_ for details) as a
|
|
base class. This adds a small bit of information to ``Child`` that allows
|
|
pybind11 to realize that there is already an existing
|
|
``std::shared_ptr<...>`` and communicate with it. In this case, the
|
|
declaration of ``Child`` should look as follows:
|
|
|
|
.. _cppreference: http://en.cppreference.com/w/cpp/memory/enable_shared_from_this
|
|
|
|
.. code-block:: cpp
|
|
|
|
class Child : public std::enable_shared_from_this<Child> { };
|
|
|
|
.. _smart_pointers:
|
|
|
|
Custom smart pointers
|
|
=====================
|
|
|
|
pybind11 supports ``std::unique_ptr`` and ``std::shared_ptr`` right out of the
|
|
box. For any other custom smart pointer, transparent conversions can be enabled
|
|
using a macro invocation similar to the following. It must be declared at the
|
|
top namespace level before any binding code:
|
|
|
|
.. code-block:: cpp
|
|
|
|
PYBIND11_DECLARE_HOLDER_TYPE(T, SmartPtr<T>);
|
|
|
|
The first argument of :func:`PYBIND11_DECLARE_HOLDER_TYPE` should be a
|
|
placeholder name that is used as a template parameter of the second argument.
|
|
Thus, feel free to use any identifier, but use it consistently on both sides;
|
|
also, don't use the name of a type that already exists in your codebase.
|
|
|
|
The macro also accepts a third optional boolean parameter that is set to false
|
|
by default. Specify
|
|
|
|
.. code-block:: cpp
|
|
|
|
PYBIND11_DECLARE_HOLDER_TYPE(T, SmartPtr<T>, true);
|
|
|
|
if ``SmartPtr<T>`` can always be initialized from a ``T*`` pointer without the
|
|
risk of inconsistencies (such as multiple independent ``SmartPtr`` instances
|
|
believing that they are the sole owner of the ``T*`` pointer). A common
|
|
situation where ``true`` should be passed is when the ``T`` instances use
|
|
*intrusive* reference counting.
|
|
|
|
Please take a look at the :ref:`macro_notes` before using this feature.
|
|
|
|
By default, pybind11 assumes that your custom smart pointer has a standard
|
|
interface, i.e. provides a ``.get()`` member function to access the underlying
|
|
raw pointer. If this is not the case, pybind11's ``holder_helper`` must be
|
|
specialized:
|
|
|
|
.. code-block:: cpp
|
|
|
|
// Always needed for custom holder types
|
|
PYBIND11_DECLARE_HOLDER_TYPE(T, SmartPtr<T>);
|
|
|
|
// Only needed if the type's `.get()` goes by another name
|
|
namespace pybind11 { namespace detail {
|
|
template <typename T>
|
|
struct holder_helper<SmartPtr<T>> { // <-- specialization
|
|
static const T *get(const SmartPtr<T> &p) { return p.getPointer(); }
|
|
};
|
|
}}
|
|
|
|
The above specialization informs pybind11 that the custom ``SmartPtr`` class
|
|
provides ``.get()`` functionality via ``.getPointer()``.
|
|
|
|
.. seealso::
|
|
|
|
The file :file:`tests/test_smart_ptr.cpp` contains a complete example
|
|
that demonstrates how to work with custom reference-counting holder types
|
|
in more detail.
|