6.5 代理类
为了以更自然的方式将C++类映射到目标语言中,SWIG支持的目标语言多数情况下都会通过代理类来包装C++类。这些代理类典型情况下都使用目标语言实现。例如,如果你正在创建Python模块,那么每个C++类都会用一个相关的Python类来代理。或者,如果你正在创建一个Java模块,每个C++类也会使用一个相关的Java代理类来实现。
6.5.1 代理类的构建
代理类一般总是通过一层额外的包装层实现,该层使用底层的访问函数。为示意,假设你有一个C++类如下:
class Foo {public:Foo();~Foo();int bar(int x);int x;};
使用C++伪代码,代理类可能像这样:
class FooProxy {private:Foo *self;public:FooProxy() {self = new_Foo();}~FooProxy() {delete_Foo(self);}int bar(int x) {return Foo_bar(self,x);}int x_get() {return Foo_x_get(self);}void x_set(int x) {Foo_x_set(self,x);}};
当然,一定要记住:代理类是使用目标语言实现的。例如,在Python 中,代理类长的可能想下面这样:
class Foo:def __init__(self):self.this = new_Foo()def __del__(self):delete_Foo(self.this)def bar(self,x):return Foo_bar(self.this,x)def __getattr__(self,name):if name == 'x':return Foo_x_get(self.this)...def __setattr__(self,name,value):if name == 'x':Foo_x_set(self.this,value)...
再说一遍,重点强调一点底层的访问函数总是被代理类使用。无论什么时候,代理类都尽量使用与C++语言特性相似的的高级语言特性。这包括操作符重载、异常处理和其他特性。
6.5.2 代理类中的资源管理
代理类中主要问题是被包装对象的内存管理。有如下的C++代码:
class Foo {public:Foo();~Foo();int bar(int x);int x;};class Spam {public:Foo *value;...};
下面是脚本语言使用它们的方式:
f = Foo() # Creates a new Foos = Spam() # Creates a new Spams.value = f # Stores a reference to f inside sg = s.value # Returns stored referenceg = 4 # Reassign g to some other valuedel f # Destroy f
现在,思考产生的内存管理问题。当对象在脚本中创建时,对象被新创建的代理类包装起来。也就是说,有一个新的代理类实例和一个底层C类的新实例。在这个例子中,f和s都是用这种方式创建的。然而,声明的s.value值得关注——执行时,指向f的指针存储在另一个对象中。这意味着脚本代理类和另一个C++类共享对同一对象的引用。为了使事情更有趣,考虑语句g = s.value。在执行时,这将创建一个新的代理类g,它对存储在s.value中的C++对象提供了一层包装。通常情况下,没有办法知道对象从哪儿来——它可能从脚本中创建,但也可能是内部生成的。在这个特殊的例子中,对g的赋值是f的第二个代理类的结果。换句话说,对f的引用现在由两个代理类和一个C类共享。
最后,考虑当对象被释放时会发生什么。在语句g=4中,变量g被重新赋值。在多数语言中,这使得旧的g变量被垃圾回收。当然,仍然有一个对存储在另一个C++对象中的原始对象的引用。发生什么事了?对象仍然有效吗?
为了处理内存管理问题,代理类提供了一个控制对象所有权的的API。使用 C++伪代码,所有权控制可能看起来是这样的:
class FooProxy {public:Foo *self;int thisown;FooProxy() {self = new_Foo();thisown = 1; // Newly created object}~FooProxy() {if (thisown) delete_Foo(self);}...// Ownership control APIvoid disown() {thisown = 0;}void acquire() {thisown = 1;}};class FooPtrProxy: public FooProxy {public:FooPtrProxy(Foo *s) {self = s;thisown = 0;}};class SpamProxy {...FooProxy *value_get() {return FooPtrProxy(Spam_value_get(self));}void value_set(FooProxy *v) {Spam_value_set(self,v->self);v->disown();}...};
分析这段代码,有以下几个中心特征:
- 每个代理类保有标示对象拥有权的额外标志。只有当所有权标志设置时才能释放C++对象。
 - 当在目标语言中创建了新的对象,所有权标志就会被设置。
 - 当返回内部C++对象的引用是,它被包装成代理类,但代理类没有该引用的所有权。
 - 在某些情况下,所有权被调整。例如,当值被赋值给类的成员时,失去所有权。
 - 可使用
disown()和acquire()方法手动调整所有权。 
考虑到C内存管理的棘手特性,代理类不可能自动处理所有可能的内存管理问题。然而,代理确实提供了一种手动控制机制,可以用于(如果需要的话)解决一些更棘手的内存管理问题。
译者注解:上面这个例子需要大家仔细揣摩、细细评味,几乎所有的脚本语言、高级静态语言如Go、C#、Java等都提供自动垃圾回收机制,了解对象所有权很重要!
6.5.3 语言特定的细节
关于代理类的特定细节在每个目标语言相关章节有详细介绍。本章只是用比较通用的方式大致介绍了代理类的相关主题。
