Cython问题:从引用获取的Numpy数组缺失前两个元素
问题根源:返回局部变量的引用导致悬垂引用
你遇到的这个诡异问题,本质上是C++里一个非常经典的错误——返回了局部变量的引用,这会产生悬垂引用,进而触发未定义行为(也就是你看到的前两个元素变成0的异常情况)。
看你的cmyclass.cc里的get_vec函数:
std::vector<int>& IntVector::get_vec(){ std::vector<int> buff; buff.reserve(vec.size()); for(int i=0; i<vec.size(); ++i){ buff.push_back(vec[i]); } return buff; }
这里的buff是函数内部的局部变量,当函数执行完毕返回时,这个局部变量会被销毁,它占用的内存会被系统回收或重新分配。你返回的是这个已经被销毁的对象的引用,后续在Cython里访问这个引用指向的内存时,这块内存已经不属于原来的vector了,所以读取到的是随机的垃圾值(也就是你看到的前两个0)。
而当你把返回类型改成std::vector<int>(非引用)时,函数会把局部的buff拷贝一份(C++11及以后会触发更高效的移动语义),返回的是一个全新的、有效的vector对象,所以后续转换成Numpy数组时一切正常。
正确的实现方式:通过参数传入vector引用
你后来采用的「将vector作为参数传入」的方案是完全正确的,这也是C++里避免这类悬垂引用问题的常用手段之一。
修改后的完整代码:
cmyclass.h
#ifndef MYCLASS_H #define MYCLASS_H #include <vector> #include <string> namespace vec { class IntVector { private: std::vector<int> vec; public: IntVector(); virtual ~IntVector(); void get_vec(std::vector<int>&); }; } #endif
cmyclass.cc
#include "cmyclass.h" #include <iostream> using namespace vec; IntVector::IntVector(){ for(int i=10; i<20; ++i){ vec.push_back(i); } } IntVector::~IntVector(){ } void IntVector::get_vec(std::vector<int>& buff){ buff.reserve(vec.size()); for(int i=0; i<vec.size(); ++i){ buff.push_back(vec[i]); } }
myclass.pyx
import numpy as np cimport numpy as np from libcpp.vector cimport vector cdef extern from "cmyclass.h" namespace "vec": cdef cppclass IntVector: IntVector() except + void get_vec(vector[int]&) cdef class IntVec: cdef IntVector* _thisptr def __cinit__(self): self._thisptr = new IntVector() def __dealloc__(self): del self._thisptr def __init__(self): pass def get_vec(self): cdef vector[int] buff; self._thisptr.get_vec(buff); return np.asarray(buff)
setup.py
from distutils.core import setup from Cython.Build import cythonize from distutils.extension import Extension sourcefiles = ['myclass.pyx', 'cmyclass.cc'] compile_opts = ['-std=c++11'] ext=[Extension('*', sourcefiles, extra_compile_args=compile_opts, language='c++')] setup( ext_modules=cythonize(ext) )
为什么这个方案可行?
这里我们把外部创建的buff(在Cython代码里定义的vector)的引用传入函数,函数内部直接操作这个外部的vector对象。因为buff的生命周期由Cython代码控制(直到转换成Numpy数组之后才会销毁),所以不会出现悬垂引用的问题,读取到的内容都是有效的。
内容的提问来源于stack exchange,提问作者user3091275
相关产品推荐
相关产品推荐

