You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

通过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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.29 19:53:15