如何使用pybind11绑定返回引用的函数?
在把C++和pybind11
结合起来的时候,我遇到了一个问题,主要是关于几个类成员返回(常量或非常量)引用的情况。考虑下面这段代码:
struct Data {
double value1;
double value2;
};
class Element {
public:
Element() = default;
Element(Data values) : data(values) { }
Element(const Element& other) : data(other.data) { printf("copying from %p\n", &other); }
Data data;
};
class Container {
public:
Container() : data(10, Element(Data{0.1, 0.2})) {};
Element& operator[](size_t idx) { return data[idx]; }
const Element& operator[](size_t idx) const { return data[idx]; }
protected:
std::vector< Element > data;
};
这段代码是绑定到一个Python模块的,具体是:
py::class_< Data > (module, "Data")
.def(py::init< double, double >(), "Constructs a new instance", "v1"_a, "v2"_a)
.def_readwrite("value1", &Data::value1)
.def_readwrite("value2", &Data::value2);
py::class_< Element > (module, "Element")
.def(py::init< Data >(), "Constructs a new instance", "values"_a)
.def_readwrite("data", &Element::data)
.def("__repr__", [](const Element& e){ return std::to_string(e.data.value1); });
py::class_< Container > (module, "Container")
.def(py::init< >(), "Constructs a new instance")
.def("__getitem__", [](Container& c, size_t idx) { return c[idx]; }, "idx"_a)
.def("__setitem__", [](Container& c, size_t idx, Element e) { c[idx] = e; }, "idx"_a, "val"_a);
我在使用Container
类的[]
操作符时遇到了麻烦。
print("-------------")
foo = module.Data(0.9, 0.8)
print(foo.value2)
foo.value2 = 0.7 # works
print(foo.value2)
print("-------------")
e = module.Element(motion.Data(0.3, 0.2))
print(e.data.value1)
e.data.value2 = 0.6 # works
print(e.data.value2)
e.data = foo # works
print(e.data.value2)
print("-------------")
c = motion.Container()
print(c[0].data.value1)
c[0] = e # works
print(c[0].data.value1)
c[0].data = foo # does not work (!)
print(c[0].data.value1)
c[0].data.value1 = 0.0 # does not work (!)
print(c[0].data.value1)
虽然__getitem__
(也就是[]
)函数看起来是正常工作的,但在访问返回对象的成员时却出现了问题;相反,返回的引用会创建一个临时的副本,而对这个副本的任何修改都不会反映到原始对象上。我尝试了几种方法:1) 在绑定Element
类时声明一个std::shared_ptr<Element>
持有者类型;2) 在__getitem__
上定义特定的返回值策略py::return_value_policy::reference
和py::return_value_policy::reference_internal
;3) 定义特定的调用策略py::keep_alive<0,1>()
和py::keep_alive<1,0>()
;但这些方法都没有解决问题。
有没有什么提示可以帮助我解决这个问题?
1 个回答
2
C++会自动推断你写的lambda函数的返回类型为Element
,而不是Element&
,这就导致它会复制一个新的对象。所以你最好明确指定返回类型,并且确保以引用的方式传递参数,这样就可以避免不必要的复制。
py::class_< Container > (module, "Container")
.def(py::init< >(), "Constructs a new instance")
.def("__getitem__", [](Container& c, size_t idx) ->Element& { return c[idx]; }, "idx"_a, py::return_value_policy::reference_internal)
.def("__setitem__", [](Container& c, size_t idx, const Element& e) { c[idx] = e; }, "idx"_a, "val"_a);
如果你使用的是C++14或更高版本,可以把返回类型定义为->auto&
。