-
This is something I believe should be possible, so I'm likely making a stupid mistake somewhere. I've created a standalone case below to explain. Essentially, in a much larger project, I have For the Python bindings, I'd like to have similar syntax, e.g., my_class = container.createMyClass()
container.discardMyClass(my_class) at the end of this call #include <iostream>
#include <map>
#include <memory>
#include <format>
#include <pybind11/pybind11.h>
class MyClass {
public:
static int id;
MyClass(){
id += 1;
_id = id;
}
int get_id(){return _id;};
private:
int _id;
};
int MyClass::id = 0;
class Container {
public:
std::shared_ptr<MyClass> createMyClass() {
auto dark = std::make_shared<MyClass>();
_map[dark->get_id()] = dark;
return dark;
}
void discardMyClass(MyClass& c) {
int id = c.get_id();
if (_map.contains(id)) {
if (_map.at(id).use_count() != 1) {
throw std::invalid_argument(std::format("Reference count is too high: {}", _map.at(id).use_count()));
}
_map.erase(id);
}
}
private:
std::map<int, std::shared_ptr<MyClass>> _map;
};
namespace py = pybind11;
PYBIND11_MODULE(example, m) {
py::class_<MyClass, std::shared_ptr<MyClass>>(m, "MyClass")
.def("get_id", &MyClass::get_id);
py::class_<Container>(m, "Container")
.def(py::init<>())
.def("createMyClass", &Container::createMyClass)
.def("discardMyClass", [](Container& self, py::object obj) {
std::shared_ptr<MyClass>& dark = obj.cast<std::shared_ptr<MyClass>&>();
// std::cout << "Current reference count: " << dark.use_count() << std::endl;
MyClass* mc = dark.get();
dark.reset();
self.discardMyClass(*mc);
});
} In the Python I have a cmake_minimum_required(VERSION 3.28)
project(test)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
set(CMAKE_INSTALL_PREFIX ${CMAKE_SOURCE_DIR})
# Set compiler options
set(CMAKE_C_COMPILER clang) # C compiler
set(CMAKE_CXX_COMPILER clang++) # C++ compiler
# Set the C++ standard to 20. Add optimization level and debug symbols.
add_compile_options(-std=c++20 -ggdb3 -O0)
# Find pybind11 in the system
# This is needed for CMake < 3.27. After Cmake 3.27+, can remove setting PYBIND11_FINDPYTHON.
set(PYBIND11_FINDPYTHON ON)
find_package(pybind11 REQUIRED CONFIG)
# Find Python in the system
find_package(Python REQUIRED COMPONENTS Interpreter Development)
pybind11_add_module(example example.cc)
install(TARGETS example DESTINATION .) changing the compiler option from The Python test case I'm using is here from example import Container
c = Container()
mc = c.createMyClass()
c.discardMyClass(mc) Please let me know if there is any other helpful information I can provide. |
Beta Was this translation helpful? Give feedback.
Replies: 1 comment
-
Okay, found a way to answer my own question here. In the end, I was making a silly mistake. Let me explain.
The idea with the // Get the shared_ptr that this object is holding. We need the reference so we get
// the actual shared pointer itself and not a copy of it. To do this, we need to get
// the instance representation and pull the holder out of it. Trying to use something
// like py::cast here will not be able to give a reference to the holder.
using InstanceType = pybind11::detail::instance;
auto* instance = reinterpret_cast<InstanceType*>(obj.ptr());
auto vh = instance->get_value_and_holder(0);
std::shared_ptr<MyClass>& dark = vh.holder<std::shared_ptr<MyClass>>(); To put this all together with my example from above:
#include <iostream>
#include <map>
#include <memory>
#include <format>
#include <pybind11/pybind11.h>
#include <pybind11/detail/internals.h>
class MyClass {
public:
static int id;
MyClass(){
id += 1;
_id = id;
}
int get_id(){return _id;};
private:
int _id;
};
int MyClass::id = 0;
class Container {
public:
std::shared_ptr<MyClass> createMyClass() {
auto dark = std::make_shared<MyClass>();
_map[dark->get_id()] = dark;
return dark;
}
void discardMyClass(MyClass& c) {
int id = c.get_id();
if (_map.contains(id)) {
if (_map.at(id).use_count() != 1) {
throw std::invalid_argument(std::format("Reference count is too high: {}", _map.at(id).use_count()));
}
_map.erase(id);
}
}
private:
std::map<int, std::shared_ptr<MyClass>> _map;
};
namespace py = pybind11;
PYBIND11_MODULE(example, m) {
py::class_<MyClass, std::shared_ptr<MyClass>>(m, "MyClass")
.def("get_id", &MyClass::get_id);
py::class_<Container>(m, "Container")
.def(py::init<>())
.def("createMyClass", &Container::createMyClass)
.def("discardMyClass", [](Container& self, py::object obj) {
// Get the shared_ptr that this object is holding. We need the reference so we get
// the actual shared pointer itself and not a copy of it. To do this, we need to get
// the instance representation and pull the holder out of it. Trying to use something
// like py::cast here will not be able to give a reference to the holder.
using InstanceType = pybind11::detail::instance;
auto* instance = reinterpret_cast<InstanceType*>(obj.ptr());
auto vh = instance->get_value_and_holder(0);
std::shared_ptr<MyClass>& dark = vh.holder<std::shared_ptr<MyClass>>();
std::cout << "Current reference count: " << dark.use_count() << std::endl;
MyClass* mc = dark.get();
dark.reset();
self.discardMyClass(*mc);
// Set Python object to Py_None. This isn't really a good complete solution, because
// Py_None is supposed to be singleton, but it does avoid seg faults if the user
// tries to access the Python variable after this is called.
*obj.ptr() = *Py_None;
});
}
cmake_minimum_required(VERSION 3.28)
project(test)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
set(CMAKE_INSTALL_PREFIX ${CMAKE_SOURCE_DIR})
# Set compiler options
set(CMAKE_C_COMPILER clang) # C compiler
set(CMAKE_CXX_COMPILER clang++) # C++ compiler
# Set the C++ standard to 20. Add optimization level and debug symbols.
add_compile_options(-std=c++20 -ggdb3 -O0)
# Find pybind11 in the system
# This is needed for CMake < 3.27. After Cmake 3.27+, can remove setting PYBIND11_FINDPYTHON.
set(PYBIND11_FINDPYTHON ON)
find_package(pybind11 REQUIRED CONFIG)
# Find Python in the system
find_package(Python REQUIRED COMPONENTS Interpreter Development)
pybind11_add_module(example example.cc)
install(TARGETS example DESTINATION .)
from example import Container
c = Container()
mc = c.createMyClass()
c.discardMyClass(mc) |
Beta Was this translation helpful? Give feedback.
Okay, found a way to answer my own question here. In the end, I was making a silly mistake. Let me explain.
pybind11
has the concept of avalue
and aholder
. Theholder
is the thing being used to manage lifetimes, ashared_ptr
in this case.pybind11
often works with the raw pointer, which is called thevalue
. For example, when you access your object to call its methods, etc. from Python,pybind11
is calling these methods, etc. on a cached raw pointer, i.e., on thevalue
. This avoids calling things likeget
each time on the holder, which improves performance.The idea with the
pybind11
API is you don't need to get the holder thatpybind11
is storing. You can get copies and such, but there'…