swig-fortran / swig

This fork of SWIG creates Fortran wrapper code from C++ headers.
http://www.swig.org
Other
42 stars 11 forks source link

Rethink memory management (construction/destruction) #138

Open sethrj opened 4 years ago

sethrj commented 4 years ago

Currently, instantiating and cleaning up classes is done in a very C++-like manner, with a constructor statement and a release statement. Assignment of proxy class objects generally acts like pointer association (=> in Fortran)

type(MyProxyClass) :: owned, referenced, also_referenced
owned = MyProxyClass(1, 2, 3) ! Construct a class
referenced = get_a_ref() ! Make 'referenced' point to some C++ data
referenced = owned ! Pointer assignment, doesn't change any data

which prohibits assignment of class data; so the following can't be used to change the contents of the vector:

type(VectorString), intent(in) :: vec
type(String) :: vec_ref
vec_ref = vec%get_ref(0) ! get a reference to a C++ string
vec_ref = String("new string") ! change the reference to point to a new string, doesn't change `vec`'s data

Is there a way to allocate, assign, point to, and deallocate with SWIG proxy types? Can we make it at least as safe as the current method, and as safe as native Fortran?

For example, something like

type(MyProxyClass), allocatable :: owned
type(MyProxyClass), pointer :: referenced, also_referenced
allocate(owned, source=MyProxyClass(1, 2, 3)) ! Construct a class from fortran
referenced => get_a_ref() ! Get a proxy class pointer to C++ data
also_referenced => owned ! Make a Fortran pointer also reference the origin class
owned = referenced ! Do assignment on underlying C++ objects
dellocate(owned) ! optional??? if FINAL behaves correctly
! deallocate(referenced)??
! deallocate(also_referenced)??

I don't think that native Fortran pointers are reference-counted, so I'm not sure how to safely obtain a C++ reference and make it appear as a Fortran pointer.

sethrj commented 4 years ago

Another alternative is keeping the current "objects are pointers" paradigm and adding a %assign type-bound subroutine.

AndrewGaspar commented 3 years ago

My two cents - here's the behavior I think I'd like, though I get that this is a difficult needle to thread. Sorry, this is a bit stream-of-consciousness:

1) Fortran proxies should have reference semantics, so assignment just changes the referred object 1) Enable finalizers 1) Use subroutines for constructors, meaning you don't need the cmemflag rvalue behavior 1) Assignment is simply dangerous and must be carefully managed - if you do a = b, then you must call a routine on b to release the shared ownership of the resource 1) If you want to get access to value semantics, add an assign method that exists only when operator=(foo const&) exists 1) Change release to have the same semantics as std::unique_ptr::release - it releases ownership of the resource, but the resource still lives

Then add a separate opt-in capability for reference counted (shared_ptr) semantics that makes assignment safe.

If you want "safe" references to a proxy object for the default case, I think the most universal option that supports both creating refs to objects owned in Fortran and refs to objects owned in C++ is to define separate Ref proxies that have the same exact behavior as the "owning" proxies. Or you could perhaps invert it where you have the "ref" proxies be the default types and add a separate Owned type. In that latter case, you could perhaps just wrap the "ref" type with the "owning" type and add a finalizer.

AndrewGaspar commented 3 years ago

(To be honest, I've probably held every possible position under the sun about what the right way to design resource-owning classes in Fortran is. It would be a lot easier if Fortran had move semantics)

sethrj commented 3 years ago

I can respond more in detail later, but one the important historical note is that during most of the development of SWIG-Fortran's semantics, Gfortran's finalize support was very buggy -- I think version 8 or 9 was the first one to correctly initialize and finalize data for local (non-allocated) variables. Several of the target codes don't yet support GCC/GFortran versions that new.

AndrewGaspar commented 3 years ago

Hm... I've been happily using finalizers in GCC 7, but it's possible I haven't hit the corner cases where they're broken. It would be a good optional feature, nonetheless.