使用 SWIG 在 Python 中 C++ 类的奇怪行为

Posted

技术标签:

【中文标题】使用 SWIG 在 Python 中 C++ 类的奇怪行为【英文标题】:Strange behaviour of C++ class in Python with SWIG 【发布时间】:2017-07-30 17:28:36 【问题描述】:

我有一个带有两种方法的 C++ 类(大型项目的一部分)。这些方法的工作非常相似:第一个对向量进行归一化并返回它,而第二个则返回一个归一化的向量而不改变原始向量。

vector3.h

class Vector3

public:
  Vector3(double a = 0.0, double b = 0.0, double c = 0.0) 
      : x(a), y(b), z(c) 
  Vector3(const Vector3& other) : x(other.x), y(other.y), z(other.z) 
  double length() const  return sqrt(x*x+y*y+z*z); 
  Vector3& normalize()
  
    float_type n = length();
    n = float_type(1.0) / ( n ? n : 1.0 );
    this->x *= n;
    this->y *= n;
    this->z *= n;
    return *this;
  
  Vector3 normalized() const
  
    Vector3 v(*this);
    v.normalize();
    return v;
  
  std::string toString()
  
    std::ostringstream strm;
    strm << '(' << x << ", " << y << ", " << z << ')' ;
    return strm.str();
  
  Vector3 operator+(const Vector3& other) const
  
    return Vector3(x + other.x, y + other.y, z + other.z);
  

private:
  double x,y,z;
;

我使用 SWIG(通过 cmake)为这个类构建 Python 绑定。

vector3.i

%include <stl.i>
%include <carrays.i>
%include <cpointer.i>
%module vectortest
%
#include "vector3.h"
%
%include "vector3.h"

CMakeLists.txt

cmake_minimum_required(VERSION 2.8)

add_executable("vtest" "vtest.cpp")

find_package(SWIG)
include($SWIG_USE_FILE)

find_package(PythonLibs)
find_package(PythonInterp)

include_directories($CMAKE_SOURCE_DIR)
include_directories($PYTHON_INCLUDE_DIRS)

SET(CMAKE_SWIG_FLAGS "")
SET_SOURCE_FILES_PROPERTIES(SOURCE vector3.i PROPERTIES CPLUSPLUS ON)
SWIG_ADD_MODULE(vectortest_python python vector3.i )
SWIG_LINK_LIBRARIES(vectortest_python $PYTHON_LIBRARIES $LIBS)
set_target_properties(_vectortest_python PROPERTIES PREFIX "_" OUTPUT_NAME "vectortest")

execute_process(COMMAND $PYTHON_EXECUTABLE -c "from distutils.sysconfig import get_python_lib; print (get_python_lib())" OUTPUT_VARIABLE PYTHON_SITE_PACKAGES OUTPUT_STRIP_TRAILING_WHITESPACE)

install(TARGETS $SWIG_MODULE_vectortest_python_REAL_NAME DESTINATION $PYTHON_SITE_PACKAGES)
install(FILES $CMAKE_BINARY_DIR/vectortest.py DESTINATION $PYTHON_SITE_PACKAGES)

C++ 中的行为很好。

vtest.cpp

#include <iostream>
#include "vector3.h"

int main()

  Vector3 v1(1,0,0);
  Vector3 v2(0,1,0);

  std::cout << (v1+v2).toString() << std::endl;
  std::cout << (v1+v2).normalized().toString() << std::endl;
  std::cout << (v1+v2).normalize().toString() << std::endl;

  return 0;

输出:

(1, 1, 0)
(0.707107, 0.707107, 0)
(0.707107, 0.707107, 0)

但是,它在 Python 中的行为很奇怪:

vtest.py

#!/usr/bin/python3

from vectortest import Vector3

v1 = Vector3(1,0,0)
v2 = Vector3(0,1,0)
print( (v1+v2).toString() )
print( (v1+v2).normalized().toString() )
print( (v1+v2).normalize().toString() )

输出:

(1, 1, 0)
(0.707107, 0.707107, 0)
(0, 0.707107, 0)

第二种方法(normalized())按预期工作,但第一种方法(normalize())没有。这是什么原因造成的?有关如何解决此问题的任何建议?

【问题讨论】:

这可能是由于您的 swigged 版本中 v1 + v2 的早期破坏造成的 Vector3 对象不是 C++ Vector3` 对象,而是 swigged 包装器对象。正如@donkopotamus 所建议的那样,我相信该对象很早就被销毁了。您是否尝试在调用normalize() 之前存储矢量(v1+v2) 当然,存储v1+v2 有助于并“解决”问题。但据我所知,我的 C++ 和 Python 代码都是完全有效的,所以结果应该可以正确地执行。要么是我的代码无效,要么是 SWIG、Python 或 GCC 中存在错误。后者几乎不是这样,那我做错了什么? 【参考方案1】:

@donkopotamus 和 @JensMunk 是对的。 您的代码在 Python 中不正确,因为临时变量的生命周期不同。 C++ 临时变量 (v1+v2) 的生命周期是 C++ 中函数调用的整个序列。 Python 调用通过 SWIG 包装器对每个函数调用进行,即通过 distinct Python 对象。对于 Python:

(v1+v2) - 构造一个临时 Python 对象 (tp1 = v1 + v2),其生命周期与调用顺序不同。

(v1+v2).normalized() - 产生另一个临时 Python 对象 (tp2_1);(v1+v2).normalize() - 产生另一个临时 Python 对象 (tp2_2)在内部引用第一个临时 Python 对象 (tp1)。 临时 Python 对象 (tp1 = v1 + v2) 递减引用计数器,现在可以进行垃圾回收,使 tp2_2 的内容无效

(v1+v2).normalized().toString() - 生成一个由有效 tp2_1 制成的临时 Python 对象 (tp3_1),现在可以对 tp2_1 进行垃圾回收; (v1+v2).normalize().toString() - 从可能损坏的 tp2_2生成一个临时 Python 对象 (tp3_2) ...

【讨论】:

为什么在 C++ 中无效?在 C++ 的情况下,临时对象在表达式求值的最后一步被破坏(意思是在输出之后)。我添加了一个嘈杂的析构函数,用-O0 编译代码以消除任何编译器优化,并在输出发生后调用析构函数获得正确的输出。在 Python 的情况下,我不确定规则,但它们应该是相似的,以便合理执行。 (并且-O0 的输出仍然不正确) 您对 C++ 的看法是正确的,临时变量在绑定引用的生命周期内持续存在,这是 C++ 中的表达式语句,但在 Python 中不是(请参阅更新的答案)。

以上是关于使用 SWIG 在 Python 中 C++ 类的奇怪行为的主要内容,如果未能解决你的问题,请参考以下文章

如何让 Swig 发出 C++ 超类

SWIG C++ to Python:生成 Python 列表的子类

将派生类传递给采用 SWIG Python 中的基类的函数

使用 SWIG 在 Python 中公开 std::list 成员

SWIG:你能否使用 SWIG 专门使用 C++ 头文件使 C++ 在 Python 中可用?

Python 抱怨 SWIG 模块不存在