 |
파이썬 마을 우리나라 파이썬 사용자들의 이야기 마을
|
|
| 이전 주제 보기 :: 다음 주제 보기 |
| 글쓴이 |
메시지 |
jrcho
가입: 올린 글: 56
|
올려짐: 2004 5월 12 7:13 pm 주제: [SWIG강좌] 3.3 C++에서의 사용 (2) |
|
|
3.3.3 Object Ownership & Memory Management
앞서에서 C++ 클래스는 이에 대응하는 Python Proxy 클래스가 존재한다고 설명하였다. 예를 들어, Vector라는 C++ 클래스를 SWIG로 변환할 경우 동일한 이름을 갖는 Vector라는 Python 클래스가 high level wrapper로 생성된다. 만약 Python 인터프리터에서 Vector라는 클래스를 생성한다면,
>>> v = Vector()
Proxy 클래스 객체와 C++ 클래스 객체가 동시에 메모리에서 생성되게 된다. 이때 C++ 객체는 C++언어에서 생성 및 삭제가 제어되는 객체로 underlying C++ 객체라고 한다. Proxy 객체는 Python 언어에서 생성 및 삭제를 제어되는 객체이다. 그렇다면 만약 Python에서 생성된 객체 v를 삭제하면 어떻게 될까? Python에서 객체의 삭제는 가비지 컬렉션을 통해 자동으로 수행된다. 만약 명시적으로 삭제하고 싶다면, del 명령을 사용한다.
>>> del v
Python에서 객체가 삭제되면, Proxy 객체가 삭제될 것이고, 동시에 underlying C++ 객체가 삭제될 것인지를 결정해야 한다. 이를 제어하는 멤버 변수가 Proxy 클래스의 thisown이라는 멤버이다. thisown은 SWIG에서 생성되는 Proxy 클래스에 자동으로 생성되는 멤버 변수로 설정(1)되거나 해제(0)되는 flag이다. thisown flag이 설정되어 있으면(1이면), Proxy 객체가 삭제될 때 자신이 wrapping하고 있는 underlying C++ 객체도 삭제한다. 만약 설정되어 있지 않으면(0이면), Proxy 객체만 지우고, underlying C++ 객체는 삭제하지 않고 놔둔다. SWIG에서 생성된 코드가 thisown flag과 관련해 메모리를 관리하는 규칙은 다음과 같다.
(1) Proxy 클래스는 ownership을 나타내는 thisown flag를 멤버로 가지고 있고, underlying C++ 객체는 이 flag이 설정되어 있을 때만 삭제된다.
(2) Proxy 객체가 Proxy 클래스의 생성자로 생성되면, thisown flag는 설정된다.
예) a = A()
(3) 객체가 by value로 리턴되면(return by value), thisown flag는 설정된다.
예) A getA() in C++, a = getA() in Python
(4) 객체가 by reference로 리턴되면(return by reference), thisown flag는 설정되지 않는다. C++ 클래스의 멤버함수함수가 자신의 멤버 객체의 레퍼런스를 리턴할 때도 마찬가지다.
예 1) A* getA() in C++, a = getA() in Python
예 2)A& getA() in C++, a = getA() in Python
예 3) class B{ public : A* getA();} in C++, a = b.getA() in Python
(5) 클래스의 포인터 멤버변수로 assign되면, ownership을 잃는다. 그 외의 경우 ownership을 유지한다. 즉, 클래스의 멤버 변수로 assign되거나, 클래스 멤버변수의 인자로 전달될 때(by value, by reference) ownership을 유지한다.
예 1) class B{ public: A *a; } in C++, b.a = c in Python → c.thisown = 0
예 2) class B{ public: A a; } in C++, b.a = c in Python → c.thisown = 1
예 3) class C{ public: void setA(A a); void setPA(A* a)} in C++
c.setA(a) → a.thisown = 1
c.setPA(a) → a.thisown = 1
다음의 예(example/thisown)을 보길 바란다.
| 코드: |
// example.i
%module example
%{
class A
{
public:
A() { printf("default constructor\n"); }
A(const A& target){printf("copy constructor\n"); }
~A(){ printf("destructor\n");}
};
A create1() { A temp; return temp; }
A* create2() { return new A; }
A& create3() { A temp; return temp; }
class B1
{
public:
A a;
};
class B2
{
public:
A *pa;
};
class C
{
public:
void setA(A a) { this->a = a; }
void setPA(A* pa) { this->pa = pa; }
private:
A a;
A *pa;
};
%}
class A
{
public:
A();
A(const A& target);
~A();
};
A create1() { A temp; return temp; }
A* create2() { return new A; }
A& create3() { A temp; return temp; }
class B1
{
public:
A a;
};
class B2
{
public:
A *pa;
};
class C
{
public:
void setA(A a);
void setPA(A* pa);
private:
A a;
A *pa;
};
|
SWIG를 실행하고, 컴파일 한 후 다음을 실행해 보라.
> python
>>> from example import *
>>> a = A() Proxy object와 underlying C++ object 생성
default constructor
>>> a.thisown
1
>>> del a Proxy object가 삭제될 때 underlying C++ object도 삭제(thisown = 1)
destructor
>>> a = A()
default constructor
>>> a.thisown = 0
>>> del a Proxy object가 삭제될 때 underlying C++ object는 삭제안됨(thisown = 0)
>>> ^Z Python 종료
삭제안된 underlying C++ object에 의한 memory leak
>
위에서, thisown의 값에 따라 어떻게 underlying C++ 객체가 삭제되는 지를 나타내고 있다. thisown의 값을 수동으로 조정할 수 있지만 조심스럽게 사용해야 한다. 위의 예에서 thisown = 0으로 설정하면, underlying C++ 객체가 삭제되지 않으므로 memory leak이 발생한다. 다음 실행 예는 함수에 의해 리턴되는 객체에 메모리 관리와 연관된 부분이다.
>>> from example import *
>>> a = create1() A create1()
>>> a.thisown
1
>>> b = create2() A* create2()
>>> b.thisown
0
>>> c = create3() A& create3()
>>> b.thisown
0
함수에 의해 underlying C++ 객체가 리턴되면 Proxy 객체가 생성되며 이 underlying C++ 객체를 wrapping 하게 된다. 리턴되는 객체가 by value로 리턴되면, thisown = 1로 설정되지만, by reference로 리턴되면 thisown = 0이다. 특히 by reference로 리턴할 때 주의해야 한다. 다음 예를 보길 바란다.
class B{
A* createA(); // new로 생성해서 리턴
A* getA(); // 내부의 포인터를 단순히 리턴
}
createA() 함수는 new 연산자를 이용해 C++ 객체를 생성해서 리턴하고, getA() 함수는 단순히 내부의 포인터를 리턴하는 멤버함수라고 하자. 두 함수 모두 thisown = 0으로 설정된 Proxy 객체를 Python에서 리턴할 것이다. 즉,
>>> a1 = b.createA()
>>> a1.thisown
0
>>> a2 = b.getA()
>>> a2.thisown
0
위에서 getA()로 리턴받은 a2는 삭제될때 Proxy 클래스만 삭제되므로 문제가 없다. 하지만, createA()로 생성된 객체 a1은 자신이 삭제될 때 underlying C++ 객체를 삭제하지 않으므로 memory leak이 발생한다. 이를 해결하기 위해서는 thisown을 강제로 설정해 주어야 할 것이다. 즉,
>>> a1 = b.createA()
>>> a1.thisown = 1
와 같이 해야 a1이 삭제될 때 underlying C++ 객체도 삭제된다.
다음으로 멤버변수에 직접 assign을 하는 경우에 대해 살펴보기로 한다.
>>> from example import *
>>> a1 = A()
>>> a2 = A()
>>> b = B()
>>> b.a = a1 ownership reserved
>>> a1.thisown
1
>>> b.pa = a2 ownership stealed → memory leak
>>> a1.thisown
0
포인터로 선언된 member 변수에 assign할 때 ownership을 잃게 되고, 하지만 포인터가 아닌 객체로 선언된 member 변수에 assign할 때 ownership은 유지된다. 포인터로 선언된 멤버변수의 경우 ownership을 잃기 때문에 사용할 때 대입된 객체에 대해 underlying C++ 객체의 소거는 소멸자 등을 통해 C++에서 이루어 져야 한다. 실제로 C++에서 public 멤버로 정의하여 사용하는 경우가 드물기 때문에 멤버변수에 assign을 하는 경우에 대해 신경 쓸 일이 거의 없다.
다음으로 C++ 클래스의 private 멤버를 억세스하는 함수에 관해 알아보겠다.
>>> from example import *
>>> a1 = A()
>>> a2 = A()
>>> c = C()
>>> c.setA(a1) ownership reserved
>>> a1.thisown
1
>>> c.setPA(a2) ownership reserved
>>> a1.thisown
1
by value 혹은 by reference로 인자로 넘기는 경우 SWIG는 모두 내부적으로 포인터로 변환해 넘기게 된고, ownership은 변화없이 유지된다. By reference로 인자를 넘기는 것 역시 ownership을 유지하기 때문에 처리에 주의를 기울여야 한다. 예를 들어
class List{
public:
append(A* a); // 내부의 포인터 배열에 포인터를 대입
};
와 같이 포인터를 저장하는 List 클래스가 있다고 하고, 이를 이용해 다음과 같은 Python함수를 작성했다고 하자.
def addTo(list):
a = A() # a has local scope
list.append(a) # a is destroyed
return list
위 함수를 Python에서
>>> list = addTo(list)
와 같이 호출하면 오류메세지가 발생한다. 위에서 a는 local scope를 가지기 때문에 addTo함수를 벗어날 때 파괴되고, 동시에 underlying C++ 객체도 같이 파괴되기 때문이다. 따라서 올바른 연산을 위해서는
def addTo(list):
a = A() # a has local scope
list.append(a)
a.thisown = 0
return list
와 같이 코드를 수정해야 한다. 이와 같이 다른 객체를 담을 수 있는 컨테이너로 사용되는 클래스를 다룰 때는 조심해서 다루어야 한다.
복잡한 클래스 라이브러리를 SWIG로 변환할 때, 메모리 관리에 관해 특히 유의해야 한다. thisown을 비롯한 메모리와 관련된 연산을 typemap을 사용하여 앞서의 디폴트 연산을 변화시킬 수 있다. 예를 들어 객체 포인터를 리턴하는 함수가 단순히 내부 객체의 포인터를 리턴하는 것이면 디폴트 연산이 옳다. 하지만 객체를 생성하고 생성된 객체의 포인터를 리턴한다면 thisown을 조정해 주어야 한다. 이때 typemap을 사용하여 Python 인터프리터에서 thisown을 조정하지 않아도 되도록 할 수 있다. 이 부분에 대한 설명은 myFEM의 Python extension을 작성하는 절을 참조하길 바란다.
3.3.4. Nested Objects
SWIG는 Nested object역시 자연스러운 방법으로 지원하고 있다. 다음 예(example/nested)를 보길 바란다.
| 코드: |
// example.i
%module example
%{
struct Vector{
double x,y,z;
};
class Particle
{
public:
Particle(){}
~Particle(){}
int type;
Vector r,v,f;
};
%}
struct Vector{
double x,y,z;
};
class Particle
{
public:
Particle();
~Particle();
int type;
Vector r,v,f;
};
|
>>> from example import *
>>> p = Particle()
>>> p
<C Particle instance at _181c7f00_p_Particle>
>>> p.r
<C Vector instance at _201c7f00_p_Vector>
>>> p.r.x = 0.0
>>> p.r.y = -7.5
>>> p.r.z = -1.0
>>> print p.r.y
-7.5
>>> p.v = p.r
>>> print p.v.y
-7.5
>>> |
|
| 위로 |
|
 |
|
|
새로운 주제를 올릴 수 없습니다 답글을 올릴 수 없습니다 주제를 수정할 수 없습니다 올린 글을 삭제할 수 없습니다 투표를 할 수 없습니다
|
|