通过pybind在C++中存储numpy的Eigen::Ref失败问题求助
问题:C++类存储numpy数组的Eigen::Ref引用后地址不匹配、数据异常
问题描述
尝试在C++类中存储numpy数组转换后的Eigen::Ref<Eigen::VectorXd>引用,插入阶段Python数组地址与Eigen::Ref的地址匹配,但调用get方法时地址失效,返回未初始化数据。以下是可复现代码及输出:
C++代码
#include <Eigen/Dense> #include <optional> #include <functional> #include <iostream> #include <pybind11/pybind11.h> #include <pybind11/eigen.h> namespace py = pybind11; template<typename VecType= Eigen::Ref<Eigen::VectorXd>> struct A { void insert(VecType& val) { std::cout<<"AddressOfValue: "<<val.data()<<std::endl; v.emplace(val); std::cout<<"AddressOfValueInserted: "<<v->get().data()<<std::endl; } VecType& get() { std::cout<<"AddressOfValueInGetter: "<<v->get().data()<<std::endl; return v->get(); } std::optional<std::reference_wrapper<VecType>> v; }; PYBIND11_MODULE( mod, m ) { auto lv2= py::class_<A<>>(m, "A"); lv2.def(py::init()); lv2.def( "insert", [] (A<>& req, Eigen::Ref<Eigen::VectorXd> solVec ) { req.insert( solVec ); } ); lv2.def( "get", [] (A<>& req ) { return req.get( ); } ); }
Python测试代码
import mod import numpy as np a = mod.A() d = np.zeros(10) d[0] = 0.1 print('Python address: {}'.format(hex(d.__array_interface__['data'][0]))) a.insert(d) print('Python addressAfter: {}'.format(hex(d.__array_interface__['data'][0]))) d2 = a.get() print(len(d2)) print(d2[0]) print('Python addressAfterd: {}'.format(hex(d.__array_interface__['data'][0]))) print('Python addressAfterd2: {}'.format(hex(d2.__array_interface__['data'][0])))
执行输出
1: AddressOfValue: 0x5607dccbdcd0 1: AddressOfValueInserted: 0x5607dccbdcd0 1: AddressOfValueInGetter: 0x7ffc258a654f 1: Python address: 0x5607dccbdcd0 1: Python addressAfter: 0x5607dccbdcd0 1: 94591762887704 1: 0.0 1: Python addressAfterd: 0x5607dccbdcd0 1: Python addressAfterd2: 0x7ffc258a654f
错误根源
你对Eigen::Ref的本质存在理解偏差:
Eigen::Ref不是原生C++引用,而是一个轻量级视图类,内部包含指向原始数据的指针、步长、维度等元信息,本身不拥有数据所有权。- pybind11在调用
insert时,会自动创建一个临时的Eigen::Ref对象来包装numpy数组,这个临时对象的生命周期仅局限于insert函数调用期间。 - 你存储的
std::reference_wrapper<VecType>指向的是这个临时Eigen::Ref对象,函数调用结束后临时对象被销毁,后续调用get时引用的是已释放的内存,导致地址异常、数据错误。
解决思路与修正方案
核心原则:不要存储Eigen::Ref的引用,而是存储能持久指向numpy原始内存的结构,并确保numpy数组的生命周期不提前结束。
方案1:使用Eigen::Map存储内存映射
Eigen::Map是直接映射到原始内存的值类型,只要原始numpy数组存在,它就能正确访问数据。
修改后的C++代码:
#include <Eigen/Dense> #include <optional> #include <iostream> #include <pybind11/pybind11.h> #include <pybind11/eigen.h> namespace py = pybind11; struct A { void insert(const Eigen::Ref<const Eigen::VectorXd>& val) { std::cout << "AddressOfValue: " << val.data() << std::endl; // 用Eigen::Map直接绑定原始内存 v.emplace(val.data(), val.size()); std::cout << "AddressOfValueInserted: " << v->data() << std::endl; } Eigen::Map<Eigen::VectorXd> get() { std::cout << "AddressOfValueInGetter: " << v->data() << std::endl; return *v; } std::optional<Eigen::Map<Eigen::VectorXd>> v; }; PYBIND11_MODULE(mod, m) { auto lv2 = py::class_<A>(m, "A"); lv2.def(py::init()); lv2.def("insert", &A::insert); lv2.def("get", &A::get); }
注意:需确保Python中的numpy数组在C++对象使用期间不被resize或重新分配内存,否则Eigen::Map的指针会失效。
方案2:持有numpy对象引用防止GC回收
如果需要确保numpy数组的生命周期与C++对象绑定,可以直接存储pybind11的py::object来持有numpy数组,避免Python垃圾回收机制释放内存。
修改后的C++代码:
#include <Eigen/Dense> #include <optional> #include <iostream> #include <pybind11/pybind11.h> #include <pybind11/eigen.h> namespace py = pybind11; struct A { void insert(py::array_t<double> arr) { Eigen::Map<Eigen::VectorXd> map(arr.mutable_data(), arr.size()); std::cout << "AddressOfValue: " << map.data() << std::endl; // 持有numpy数组对象,防止被Python垃圾回收 np_arr = std::move(arr); v.emplace(np_arr.mutable_data(), np_arr.size()); std::cout << "AddressOfValueInserted: " << v->data() << std::endl; } Eigen::Map<Eigen::VectorXd> get() { std::cout << "AddressOfValueInGetter: " << v->data() << std::endl; return *v; } std::optional<Eigen::Map<Eigen::VectorXd>> v; py::object np_arr; // 持有numpy数组引用 }; PYBIND11_MODULE(mod, m) { auto lv2 = py::class_<A>(m, "A"); lv2.def(py::init()); lv2.def("insert", &A::insert); lv2.def("get", &A::get); }
此方案中,即使Python中原始变量d被重新赋值,只要C++对象a存在,numpy数组的内存就不会被释放。
内容的提问来源于stack exchange,提问作者rath3t
相关产品推荐
相关产品推荐

