Swig: Extra objects being created


#1

I’m finding that accessor methods on a wrapped C++ class end up creating
extra objects on the Ruby side. Here’s an example to illustrate:

The following Ruby code uses two wrapped C++ classes (Point and Edge) in
the shared library TSP.so. An
Edge has two Point objects and the ‘start_node’ and ‘end_node’ methods on
Edge return Point objects.

#test_point.rb
require ‘TSP’

include TSP

def count_points
np = 0
ObjectSpace.each_object(Point){|p|
np+=1
}
np
end

pa = Point.new(2,3)
pb = Point.new(10,6)

puts “number of points should be 2: count_points=#{count_points}”

edge = Edge.new(pa,pb,(pa-pb))

puts “number of points should be 2: count_points=#{count_points}”

puts edge.start_node #<-here’s where the trouble starts
puts edge.end_node
puts “number of points should be 2: count_points=#{count_points}”
#end test_point.rb

Running test_point.rb prints:
making a new Point at 2,3
making a new Point at 10,6
number of points should be 2: count_points=2
number of points should be 2: count_points=2
(2,3)
(10,6)
number of points should be 2: count_points=4

So after calling the accessor methods ‘start_node’ and ‘end_node’ the
number of Point objects is 4 instead of 2. I think this is being caused
by SWIG_NewPointerObj. Maybe there’s no way around it. I would have
expected that I would get a reference to an already existing Point object
instead of a new Point object being created.

Is there any way to do this so that no new Point objects are created when
calling Edge#end_node, Edge#start_node? Since my script calls these
methods a lot, lots of extra Point objects get created.

Here are the point.h/cpp and edge.h/cpp files:

#include
class Point
{
// attr_accessor :x, :y
private:
int x_;
int y_;

//Point(Point& x){};

public:

Point(int x,int y):x_(x),y_(y){std::cout << "making a new Point at " 

<< x <<","<<y<< std::endl;}
Point(Point& p){std::cout << “copy constructor” << std::endl;}

//calculate the distance between this point and other point
float operator -(Point& o);

bool operator ==(Point& o);

//accessors:
int x(){ return x_; }
int y(){ return y_; }

};

float Point::operator -(Point& o){
return sqrt((x_ - o.x())(x_ - o.x())+(y_-o.y())(y_ - o.y()));
};

bool Point::operator ==(Point& o){
return ((x_==o.x())&&(y_==o.y()));
};

#include "point.h"
class Edge
{
private:
Point* start_node_;
Point* end_node_;
float length_;
float tao_;
float quality_factor_;

public:
//constructor:

Edge(Point* s_node, Point* e_node, float len, float tao=TAO0);

//accessors
float length() const{return length_;}
Point* start_node() const {return start_node_;}
Point* end_node() const {return end_node_;}
float quality_factor() const { return quality_factor_;}
float tao() const {return tao_;}
void set_tao(float new_tao){ tao_ = new_tao; }

void calc_quality_factor();

};

#include “edge.h”
#include <math.h>

Edge::Edge(Point* s_node, Point* e_node, float len, float
tao):start_node_(s_node),end_node_(e_node),length_(len),tao_(tao){
this->calc_quality_factor();
}

void Edge::calc_quality_factor(){
quality_factor_=(tao_ * (pow((1.0/length_),BETA)) );

}
///end C++ code

Here is the TSP.i file:

%module TSP
%{
#include “constants.h”
#include “point.h”
#include “edge.h”
%}
%alias Edge::set_tao “tao=”;
%include “constants.h”
%include “point.h”
%include “edge.h”


(Lyle Johnson) #2

Phil Tomson wrote:

I’m finding that accessor methods on a wrapped C++ class end up creating
extra objects on the Ruby side. Here’s an example to illustrate:

So after calling the accessor methods ‘start_node’ and ‘end_node’ the
number of Point objects is 4 instead of 2. I think this is being caused
by SWIG_NewPointerObj. Maybe there’s no way around it. I would have
expected that I would get a reference to an already existing Point object
instead of a new Point object being created.

No, you are correct that every time the accessor (or any other method
that returns a pointer to some object) is called, you’ll get a new Ruby
instance that wraps that C++ object.

Is there any way to do this so that no new Point objects are created when
calling Edge#end_node, Edge#start_node? Since my script calls these
methods a lot, lots of extra Point objects get created.

I have a workaround for this in FXRuby, but it is a little complicated.
Generally speaking, I maintain a little hash table that maps the C++
pointers to the corresponding Ruby objects. When it’s time to return a
Ruby object from one of these accessors, I first check that hash table
to see if there’s already a “live” Ruby instance corresponding to the
underlying C++ object and, if so, return that Ruby instance. Otherwise,
I call SWIG_NewPointerObj() as usual and store this new association in
the hash.

It might be possible to extend SWIG to automate this process, I’m not
sure. We’d have to think about the implications for garbage collection
as well; for example, if the Ruby object gets garbage-collected, we need
to be sure to remove it from the hash table since it’s no longer “alive”.


#3

In article 40279508.7050100@knology.net,

Phil Tomson wrote:

I’m finding that accessor methods on a wrapped C++ class end up creating
extra objects on the Ruby side. Here’s an example to illustrate:

So after calling the accessor methods ‘start_node’ and ‘end_node’ the
number of Point objects is 4 instead of 2. I think this is being caused
by SWIG_NewPointerObj. Maybe there’s no way around it. I would have
expected that I would get a reference to an already existing Point object
instead of a new Point object being created.

No, you are correct that every time the accessor (or any other method
that returns a pointer to some object) is called, you’ll get a new Ruby
instance that wraps that C++ object.

Is there any way to do this so that no new Point objects are created when
calling Edge#end_node, Edge#start_node? Since my script calls these
methods a lot, lots of extra Point objects get created.

I have a workaround for this in FXRuby, but it is a little complicated.
Generally speaking, I maintain a little hash table that maps the C++
pointers to the corresponding Ruby objects. When it’s time to return a
Ruby object from one of these accessors, I first check that hash table
to see if there’s already a “live” Ruby instance corresponding to the
underlying C++ object and, if so, return that Ruby instance. Otherwise,
I call SWIG_NewPointerObj() as usual and store this new association in
the hash.

It might be possible to extend SWIG to automate this process, I’m not
sure. We’d have to think about the implications for garbage collection
as well; for example, if the Ruby object gets garbage-collected, we need
to be sure to remove it from the hash table since it’s no longer “alive”.

Lyle,

Since a lot of my outgoing email seems to be bouncing today for whatever
reason, I’ll try posting here…

I hand edited the wrap file so that no new objects are being created.
Basically, I just added some Ruby instance variables to shadow the C++
ones. Here’s the Edge constructor:

static VALUE
_wrap_new_Edge(int argc, VALUE *argv, VALUE self) {
Point *arg1 = (Point *) 0 ;
Point *arg2 = (Point ) 0 ;
float arg3 ;
float arg4 = (float) 1.0/(80
8000) ;
Edge *result;

if ((argc < 3) || (argc > 4))
rb_raise(rb_eArgError, "wrong # of arguments(%d for 3)",argc);
SWIG_ConvertPtr(argv[0], (void **) &arg1, SWIGTYPE_p_Point, 1);
SWIG_ConvertPtr(argv[1], (void **) &arg2, SWIGTYPE_p_Point, 1);
arg3 = (float) NUM2DBL(argv[2]);
if (argc > 3) {
    arg4 = (float) NUM2DBL(argv[3]);
}
result = (Edge *)new Edge(arg1,arg2,arg3,arg4);
DATA_PTR(self) = result;
//added the following two lines:
rb_iv_set(self,"@start_node",argv[0]);
rb_iv_set(self,"@end_node",argv[1]);
return self;

}

Here’s one of the accessors:

static VALUE
_wrap_Edge_start_node(int argc, VALUE *argv, VALUE self) {
Edge *arg1 = (Edge *) 0 ;
Point *result;
VALUE vresult = Qnil;

if ((argc < 0) || (argc > 0))
rb_raise(rb_eArgError, "wrong # of arguments(%d for 0)",argc);
//replace these lines:
//SWIG_ConvertPtr(self, (void **) &arg1, SWIGTYPE_p_Edge, 1);
//result = (Point *)((Edge const *)arg1)->start_node();
//vresult = SWIG_NewPointerObj((void *) result, SWIGTYPE_p_Point,0);

//with this one:
vresult = rb_iv_get(self,"@start_node");
return vresult;

}

Works great.

Is there any way to change Swig’s behavior so that I can automate this so
I don’t have to go in an hand-edit the _wrap file?

I imagine that I need to change some swg files under the SWIG/Lib/Ruby
directory, but I’m not sure where to start. Pointers would be much
appreciated.

Phil

···

Lyle Johnson lyle@knology.net wrote: