Search code examples
c++smart-pointerspybind11

Design advice: `unique_ptr` and pybind11


I have a setup in which a resource R may be passed through various filters F also deriving from R. It makes no sense to share ownership of an R because the resource is consumed upon retrieval. Some I decided to use unique_ptr as follows:

struct R{
  virtual int yield() = 0;
};
struct F{
  F(std::unique_ptr<R> r): _r(std::move(r)) {}
  int yield(){return _r->yield() + 1;}
  std::unique_ptr<R> _r;
};

except that of course the classes are more interesting. Now I am trying to bind that with pybind11. Pybind seems not to like unique_ptr, as stated in the docs. How can I circumvent this? I see to possibilities:

  1. Replacing the std::unique_ptr by something pybind11 understands. That would mean that on both the C++ and the Python side, an R can be fed into F although the R has already been consumed by some other F.
    1. Instead of taking std::unqique_ptr, take R*&. Then transfer of ownership can be reflected with setting the parameter to zero. Seems not very C++y, though.
  2. Writing a wrapper around F that needs no unique_ptr. This would have the same problem, but only on the Python side. I could implement something taking care of this, probably. Plus, I wouldn't need to change the interface of existing code. Downside, I need to replicate all methods I need.

Both solutions don't seem too appealing. What would be an alternative?


Solution

  • One of the issues of language interop is that you have to obey the data model of both languages. Python doesn't have the conception of unique ownership of a resource, which is why pybind doesn't let you have std::unique_ptr as parameters exposed to Python.

    I think the easiest way would be to use std::shared_ptr, but comment that this is only for interop with the Python binding. You can still have F::F(std::unique_ptr<R> r) for the C++ side, because there is a converting constructor std::shared_ptr<R>::shared_ptr(std::unique_ptr<R>).