pybind / pybind11

Seamless operability between C++11 and Python
https://pybind11.readthedocs.io/
Other
15.83k stars 2.12k forks source link

[QUESTION] How to expose pointer member with boost::python behavior #3107

Open Grantim opened 3 years ago

Grantim commented 3 years ago

Hello! I am migrating from boost::python to pybind11 and have just faced some pointer member behavior issue:

class SomeClass
{
};
struct SomeStruct
{
public:
    SomeClass* scPtr;
};

void foo( SomeStruct& str )
{
    if ( str.scPtr )
        *str.scPtr;
}

// pybind11 version
PYBIND11_MODULE( mod, m )
{
    pybind11::class_<SomeClass>( m, "SomeClass" ).def( pybind11::init<>() );
    pybind11::class_<SomeStruct>( m, "SomeStruct" ).def( pybind11::init<>() ).
        def_readwrite( "scPtr", &SomeStruct::scPtr);
    m.def( "foo", &foo );
}

// boost python version
BOOST_PYTHON_MODULE( mod )
{
    boost::python::class_<SomeClass>( "SomeClass" );
    boost::python::class_<SomeStruct>( "SomeStruct" ).
        add_property( "scPtr", 
            boost::python::make_getter( &SomeStruct::scPtr, boost::python::return_value_policy<boost::python::reference_existing_object>() ),
            boost::python::make_setter( &SomeStruct::scPtr, boost::python::return_value_policy<boost::python::reference_existing_object>() ) );
    boost::python::def( "foo", &foo );
}

Python behavior

import mod
#boost version
stru = mod.SomeStruct()
stru.scPtr = mod.SomeClass()
mod.foo(stru) #works ok, but in pybind this will cause access violation (as far as scPtr will be spoiled)

#pybind version
stru = mod.SomeStruct()
sc = mod.SomeClass()
stru.scPtr = sc
mod.foo(stru) #works ok

So can I make pybind11 work as boost python?

ljluestc commented 4 days ago

#include <pybind11/pybind11.h>

class SomeClass {
public:
    SomeClass() = default;
    // Other members...
};

struct SomeStruct {
    SomeClass* scPtr = nullptr;
    // Other members...
};

void foo(SomeStruct& str) {
    if (str.scPtr) {
        // Use str.scPtr...
    }
}

PYBIND11_MODULE(mod, m) {
    pybind11::class_<SomeClass>(m, "SomeClass")
        .def(pybind11::init<>());

    pybind11::class_<SomeStruct>(m, "SomeStruct")
        .def(pybind11::init<>())
        .def_readwrite("scPtr", &SomeStruct::scPtr, pybind11::return_value_policy::reference_existing_object);

    m.def("foo", &foo);
}
Grantim commented 3 days ago

Thanks! But it seems that pybind11::return_value_policy::reference_existing_object is not present in pybind11. If somebody will come across this issue, such way might work:


class SomeClass
{
};
struct SomeStruct
{
public:
    SomeClass* scPtr;
};

void foo( SomeStruct& str )
{
    if ( str.scPtr )
        *str.scPtr;
}

// pybind11 version
PYBIND11_MODULE( mod, m )
{
    pybind11::class_<SomeClass>( m, "SomeClass" ).def( pybind11::init<>() );
    pybind11::class_<SomeStruct>( m, "SomeStruct" ).def( pybind11::init<>() ).
        def_property( "scPtr", 
            pybind11::cpp_function( []( const SomeStruct& self ) { return self.scPtr; } ),
            pybind11::cpp_function( []( SomeStruct& self, SomeClass* ptr ) { self.scPtr = ptr }, pybind11::keep_alive<0,1>() )
    );
    m.def( "foo", &foo );
}