6

I am quite a beginner in OOP with Fortran and I am trying to write a program with procedures that deal with polymorphic variables as arguments. Although my original code is much more complicated (many procedures, several derived types etc), I could isolate a simple example of my problem, say: I have a procedure that copies a polymorphic variable and slightly modifies this copy.

I was able to successfully write my test program using a subroutine:

MODULE my_module

type :: my_type
    real :: data
endtype my_type

type, extends(my_type) :: my_derived_type
end type my_derived_type

CONTAINS

subroutine sub_copy(old,new) 
implicit none
class(my_type), intent(in) :: old
class(my_type), allocatable, intent(out) :: new
allocate(new, source = old)
new%data = new%data + 1
end subroutine sub_copy

END MODULE my_module

PROGRAM my_prog
use my_module
implicit none
type(my_derived_type) :: x
class(my_type), allocatable :: y

x%data = 1.0
call sub_copy(x,y)
print*,y%data
deallocate(y)

END PROGRAM my_prog

This performs nicely both regarding the expected result and the memory allocation/deallocation.

However, I have been fighting for days trying to make working a Fortran function that would do the same job.

It seems that a function defined in a similar way to the subroutine (see here after) cannot be used simply as

y = fun_copy(x)

and my gfortran compiler (v5.0.0) complains:

Error: Assignment to an allocatable polymorphic variable at (1) is not yet supported

I have read here and there that indeed such assignment is not supported by my compiler. Waiting for that, I have tried to work that around by defining my own assignment operator (=). The following code works:

MODULE my_module

type :: my_type
    real :: data
endtype my_type

type, extends(my_type) :: my_derived_type
end type my_derived_type

interface assignment(=)
  module procedure myassign
end interface

CONTAINS

function fun_copy(old) result(new) 
implicit none
class(my_type), intent(in) :: old
class(my_type), allocatable :: new
allocate(new, source = old)
new%data = new%data + 1
end function fun_copy

subroutine myassign(new,old)
class(my_type), intent(in)  :: old
class(my_type), allocatable, intent(out) :: new
allocate(new, source=old)
end subroutine

END MODULE my_module

PROGRAM my_prog
use my_module
implicit none
type(my_derived_type) :: x
class(my_type), allocatable :: y

x%data = 1.0
y = fun_copy(x)
print*,y%data
deallocate(y)

END PROGRAM my_prog

It works in the sense that indeed, a copy of x is created as y. However, inspecting the memory budget of this simple test program (I use the Instrument software on OS X), it appears that some memory is not deallocated before the end of it. I suspect that the copy function and the assignment subroutine both allocate memory and that I only free one occurrence, leaving one allocated.

As I intend to use such a routine a large number of times in a much more complicated code, I am really concerned about memory allocation/deallocation. Of course, I can use the subroutine version of the program, but if there is a way, I would prefer the function version.

Is there a way to deal with such a problem?

Fortranner
  • 2,525
  • 2
  • 22
  • 25
Reno
  • 81
  • 3
  • In your function example, you mean `y=fun_copy(x)`, I guess. As another workaround `allocate(y,source=fun_copy(x))`, but this isn't as attractive. It would be interesting to see whether the same thing persists, though. – francescalus Apr 21 '15 at 14:20
  • @francescalus Yes I meant `y=fun_copy(x)`. I edited the original post and corrected that. Also, I just tried your workaround, and the memory allocation/deallocation problem persists. There is no change as far as I can tell. – Reno Apr 21 '15 at 15:00
  • Well, I don't (yet?) know the answer, but I suppose we can rule out defined assignment as the problem. – francescalus Apr 21 '15 at 15:06
  • `valgrind` shows the allocation in `fun_copy` as not being deallocated. The temporary created here gets passed into `myassign`, and then never gets deallocated. The only way I can see for this to work is for finalization of the temporary to occur, which still isn't implemented in gfortran as far as I know. That's assuming finalization happens the way I think it does. – dwwork Apr 21 '15 at 18:30
  • That was my guess indeed. But I am a bit surprised that it is not possible to define such a simple function in a proper way... Is there really no way to access the temporary and deallocate it manually (in the `myassign` routine for instance)? Alternatively, they might be a way using pointers instead of allocatable variables... But I couldn't figure out any nice way of doing that... – Reno Apr 21 '15 at 19:20
  • I think that pointers would still leave us with the same issue. I'm surprised that the compiler doesn't recognize that the temporary is no longer being used, since that is why allocatable variables exist (automatic memory management). If you have access to another compiler, see if that gives the same issue. What your trying to do is similar in spirit to a C++ copy constructor, which doesn't return anything, like a fortran subroutine, so I think the subroutine is the best way to go. – dwwork Apr 30 '15 at 14:51
  • 1
    I know that this would be modifying your intent somewhat, but have you tried working with a pointer, instead of an allocatable? It would sidestep the issue of a temporary getting created that you don't have the ability to manually deallocate. Instead allocate a pointer inside the function and return it, then make sure to deallocate it in the calling code. – youngmit Aug 05 '15 at 01:16

2 Answers2

2

Have you tried using pointers?

    module my_module

        implicit none

        type :: my_type
            real :: data
        contains
            procedure              :: sub_copy
            procedure              :: fun_copy_ptr
            procedure              :: fun_copy_alloc
            procedure, pass (this) :: my_assign
            generic                :: assignment(=) => my_assign
        end type my_type

        type, extends(my_type) :: my_derived_type
        end type my_derived_type

    contains

        subroutine sub_copy(this, new)
            class(my_type), intent (in)               :: this
            class(my_type), allocatable, intent (out) :: new

            allocate(new, source=this)
            new%data = new%data + 1

        end subroutine sub_copy

        function fun_copy_alloc(this) result (new)
            class(my_type), intent(in)  :: this
            class(my_type), allocatable :: new

            allocate(new, source=this)
            new%data = new%data + 1.0

        end function fun_copy_alloc

        function fun_copy_ptr(this) result (new)
            class(my_type), intent(in) :: this
            class(my_type), pointer    :: new

            allocate(new, source=this)
            new%data = new%data + 1.0

        end function fun_copy_ptr

        subroutine my_assign(new, this)
            class(my_type), intent(in)               :: this
            class(my_type), allocatable, intent(out) :: new

            allocate(new, source=this)

        end subroutine

    end module my_module

    program my_prog

        use my_module, only: &
            my_type, &
            my_derived_type

        implicit none
        type(my_derived_type)       :: x
        class(my_type), allocatable :: y
        class(my_type), pointer     :: y_ptr => null()

        x%data = 1.0

        ! Case 1
        call x%sub_copy(y)
        print *, y%data
        deallocate(y)

        ! Case 2
        y_ptr => x%fun_copy_ptr()
        print *, y_ptr%data
        deallocate(y_ptr)

        ! Case 3
        allocate( y, source=x%fun_copy_alloc() )
        print *, y%data
        deallocate(y)

    end program my_prog
jlokimlin
  • 593
  • 4
  • 9
1

This sounds similar to a question I had a while back about function vs subroutines:

fortran operator overloading: function or subroutine

I think there is a kind of trade-off between using a subroutine, which allows allocation and deallocation, and using a function, which can only allocate. I would suggest that, if the data structures are large, functions be avoided for this use and stick to subroutines.

Community
  • 1
  • 1
Charles
  • 947
  • 1
  • 15
  • 39