I have a class Type
which cannot be copied nor it contains default constructor.
I have second class A
that acts as a set of the above classes. This second class gives access via iterators and my iterator has dereference operator:
class A {
class iterator {
[...]
public:
Type & operator*()
{
return instance;
}
private:
Type instance;
}
[...]
};
Now to expose that I wrote a boost::python
code that looks like that:
class_<A>("A", [...])
.def("__iter__", iterator<A, return_internal_reference<> >())
.def("__len__", container_length_no_diff<A, A::iterator>)
;
After adding print messages to all iterator operations (construction, assignment, dereferences, destruction) for code Python like this:
for o in AInstance:
print o.key
I get output (trimmed to important part):
construct 0xffffffff7fffd3e8
dereference: 0xffffffff7fffd3e8
destroy 0xffffffff7fffd3e8
get key 0xffffffff7fffd3e8
In above code those addresses are just addresses of instance
member (or this
in method call).
First three lines are produced by iterator
, the fourth line is printed by getter method in Type
. So somehow boost::python
wraps everything in such manner that it:
- creates iterator
- dereferences iterator and stores reference
- destroys iterator (and object it contains)
- uses reference obtained in step two
So clearly return_internal_reference
does not behave like stated (note that it actually is just typedef over with_custodian_and_ward_postcall<>
) where it should keep object as long as result of method call is referenced.
So my question is how do I expose such an iterator to Python with boost::python
?
edit:
As it was pointed out it might not be clear: the original container does not contain objects of type Type
. It contains some BaseType
objects from which I am able to construct/modify Type
object. So iterator
in above example acts like transform_iterator
.
If
A
is a container that owns instances ofType
, then consider havingA::iterator
contain a handle toType
instead of having aType
:Instead of:
In python, an iterator will contain a reference to the container on which it iterates. This will extend the lifespan of an iterable object, and prevent the iterable object from being garbage collected during iteration.
boost::python
supports this behavior. Here is an example program, withFoo
being a simple type that cannot be copied;FooContainer
being an iterable container; andFooContainer::iterator
being an iterator:Here is the example output:
I think the whole problem was that I did not fully understand what semantics should
iterator
class provide. It seems that value returned by iterator has to be valid as long as container exists, not iterator.This means that
boost::python
behaves correctly and there are two solutions to that:boost::shared_ptr
A bit less efficient approaches than what I tried to do, but looks like there is no other way.
edit: I have worked out a solution (not only possible, but it seems to be working nicely): Boost python container, iterator and item lifetimes
Here is relevant sample: https://wiki.python.org/moin/boost.python/iterator.
You can return iterator value by const / non const reference:
The idea is that, as you mentioned, you should bind to the container lifetime instead of the iterator lifetime for the return value.