如何通过cython接口返回对象引用的c ++函数

时间:2018-02-27 14:01:04

标签: c++ reference cython

我正在尝试连接一个返回对象引用的C ++函数:

const blob &get_blob();

对于cython,我使用.pxd文件,让我可以访问C ++命名空间:

const blob &get_blob() except +

然后我在.pyx文件中使用该函数:

cdef const blob* o = &get_blob()

所以我使用地址运算符并赋值给指针变量。但是,在使用cython版本0.23进行编译时出现错误:

  

错误:从中分配'const blob *'         不兼容的类型'__Pyx_FakeReference *'

如果我使用cython版本0.27进行编译,则会出现 not 错误。 所以我认为这是旧的cython版本中的一个错误。

基本问题: 什么是连接通过cython返回引用的c ++函数的正确的方式。我找不到任何相关的文件。

1 个答案:

答案 0 :(得分:1)

你处理引用的方式非常好。

参考文献是Cython的一种步子。我只能推测原因,我试着解释一下:

CPython / Cython是C语言中的家,而不是C ++,C不知道引用,它们大多是指针的语法糖,不能是NULL。 C ++中的引用有一个讨厌的属性,你不能做这样的事情:

int &x;
x=some_int_var;

但必须在创建时初始化引用,并且不能再次更改引用:

int &x=some_int_var;

但是,如果你看看Cython生成的C / CPP代码,你会看到在函数开头声明所有变量的C90模式(因此,可以创建符合C90的C90模式) C代码)。为了能够使用引用,这可能意味着要为C ++更改它可能需要做很多工作。

因此,Cython通过定义一个FakeReference模板类来使用一种解决方法,该类包装一个原始指针:

template<typename T>
class __Pyx_FakeReference {
  public:
    __Pyx_FakeReference() : ptr(NULL) { }
    __Pyx_FakeReference(const T& ref) : ptr(const_cast<T*>(&ref)) { }
    T *operator->() { return ptr; }
    T *operator&() { return ptr; }
    operator T&() { return *ptr; }
    template<typename U> bool operator ==(U other) { return *ptr == other; }
    template<typename U> bool operator !=(U other) { return *ptr != other; }
  private:
    T *ptr;
};

所以对于以下愚蠢的Cython代码:

%%cython --cplus 
from libcpp.vector cimport vector
cdef set_first(vector[int] & vect):
     cdef int * first=&(vect.at(0))
     first[0]=10

我们将为行cdef int * first=&(vect.at(0))获取以下生成的C代码(仅限重要部分):

static PyObject *__pyx_f_5foo_set_first(std::vector<int>  &__pyx_v_vect) {

   # our variable first, not yet initialized (C90 style):
   int *__pyx_v_first; 

   #Fake reference, initialized with NULL  (C90 style):              
   __Pyx_FakeReference<int> __pyx_t_1; 

   #now we use implicit constructor __Pyx_FakeReference(const T& ref), 
   #be aware of const_cast
   #+ (compiler generated, which is Ok because FakeReference 
   #doesn't own the pointer) assignment-operator
   __pyx_t_1 = __pyx_v_vect.at(0);

   #now, cast FakeReference to pointer `first`
   #using operator&
   __pyx_v_first = (&__pyx_t_1);
   ...
 }

有趣且有些奇怪的是我们可以在函数签名中使用引用,如上面的set_first(vector[int] & vect)

这可能是因为传递的参数不必由Cython处理,并且在cpp-code-level上自动处理。

最后但并非最不重要的是,让我们快速检查,一切都按预期工作:

%%cython --cplus 
from libcpp.vector cimport vector
cdef set_first(vector[int] & vect):
     cdef int * first=&(vect.at(0))
     first[0]=10

def create_list(n):
   cdef vector[int] v=range(n)
   set_first(v)
   return v

>>> create_list(2)
[10,1] # yep it worked!

警告:人们可能会尝试尝试类似的事情:

%%cython --cplus 
from libcpp.vector cimport vector
cdef set_first(vector[int] & vect):
     first=vect.at(0)
     (&first)[0]=10

希望,first在某种程度上是神奇的(python?)引用。实际上,first的类型为int,最后一行是将此局部变量设置为10的复杂方式,而不是在向量中设置第一个元素的方法。以下是与上述版本的重要区别:

static PyObject *__pyx_f_5foo_set_first(std::vector<int>  &__pyx_v_vect) {

   # "int" not "int *"!
   int __pyx_v_first; 


   #now, cast FakeReference  __pyx_t_1 to int via operator() 
   #which return "*ptr" (and not "ptr" as operator&())
   __pyx_v_first = __pyx_t_1;
   ...
 }