2017-07-30 22 views
3

我有一個C++類,有兩個方法(大項目的一部分)。這些方法做了一個非常類似的工作:第一個方法歸一化矢量並返回它,而第二個方法返回一個歸一化矢量,而不改變原始矢量。Python中的C++類的奇怪行爲SWIG

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; 
}; 

我建立Python綁定這一類SWIG(通過cmake的)。

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())沒有。這是什麼造成的?有關如何解決此問題的任何建議?

+0

這可能是由V1 + v2'的'早期破壞你的swigged版本 – donkopotamus

+0

的'的Vector3造成'對象不是'C++'Vector3'對象,而是被滑動的包裝器對象。正如@donkopotamus所建議的那樣,我認爲這個對象早就被摧毀了。在調用'normalize()'之前,你有沒有試過存儲矢量'(v1 + v2)'? –

+0

當然,存儲'v1 + v2'有助於「解決」這個問題。但據我所見,我的C++和Python代碼都是完全有效的,所以結果應該可以正確執行。它要麼是我的代碼無效,要麼是SWIG,Python或GCC中存在錯誤。後者很難,所以我做錯了什麼? –

回答

0

@donkopotamus和@JensMunk是正確的。
您的代碼在Python中不正確,因爲臨時變量的生存期不同。
C++臨時變量(v1 + v2)的生存期是C++中函數調用的整個序列。通過SWIG封裝器爲每個函數調用進行Python調用,即通過Python對象執行不同的不同的。對於Python:

(v1 + v2) - 構造了一個臨時Python對象(tp1 = v1 + v2),其生命期與調用序列不同。

(v1 + v2).normalized() - 生成另一個臨時Python對象(tp2_1);
(v1 + v2).normalize() - 產生內部引用第一個臨時Python對象(tp1)的另一個臨時Python對象(tp2_2)。
臨時Python對象(tp1 = v1 + v2)遞減引用計數器,現在可以進行垃圾回收,使tp2_2的內容無效。 (v1 + v2).normalized()。toString() - 產生一個臨時Python對象(tp3_1),由有效的tp2_1生成,tp2_1現在可以被垃圾回收;
(v1 + v2).normalize()。的toString() - 產生從可能臨時Python對象(tp3_2)損壞 tp2_2
...

+0

爲什麼在C++中無效?在C++的情況下,臨時對象被破壞爲表達式評估的最後一步(意味着在輸出之後)。我添加了一個嘈雜的析構函數,用'-O0'編譯代碼以消除任何編譯器優化,並在輸出發生後調用析構函數獲得正確的輸出。在Python的情況下,我不確定規則,但它們應該類似,以便合理執行。 (並且'-O0'的輸出仍然不正確) –

+0

對於C++來說,對於綁定引用的生命週期(它是C++中的表達式語句,但不是Python中),臨時存在是持久的(請參閱更新後的答案) 。 – luart