Destructors
Overview
Teaching: 10 min
Exercises: 5 minQuestions
What is a destructor?
Objectives
Create a destructor for our two derived types to deallocate memory.
One aspect I have been ignoring until now is that the memory we allocate for our vectors is never explicitly freed by our program. So far our program has been simple enough that this is not a serious issue. We have only created a few objects that allocate memory within our main program. When the program execution has completed, that memory is returned to the operating system. However, if we had a long running loop inside our program that created new objects with allocated memory and we never deallocated that memory we would have a problem as our program would steadily increase its memory usage. This is referred to as a memory leak as was mentioned in the first half of this workshop. We can manually deallocate memory as we did with allocating memory, however there is a way to create a new special type bound procedure that is automatically called when the object goes out of scope to deallocate this memory for us. To do this we use the final keyword within the type definition.
type <type-name>
...
contains
final:: <type-finalization-subroutine>
end type
Then as we did for the display
subroutine, we can create a <type-finalization-subroutine>
to do anything that needs to be done when an object of this type goes out of scope, including deallocating memory.
Lets add destructors to deallocate our memory for our two derived types.
$ cp type_bound_procedures_select_type.f90 destructor.f90
$ nano destructor.f90
module m_vector
implicit none
type t_vector
integer:: num_elements
real,dimension(:),allocatable:: elements
contains
procedure:: display
final:: destructor_vector
end type
interface t_vector
...
end interface
type,extends(t_vector):: t_vector_3
contains
final:: destructor_vector_3
end type
interface t_vector_3
procedure:: create_size_3_vector
end interface
contains
subroutine destructor_vector(self)
implicit none
type(t_vector):: self
if (allocated(self%elements)) then
deallocate(self%elements)
endif
end subroutine
subroutine destructor_vector_3(self)
implicit none
type(t_vector_3):: self
if (allocated(self%elements)) then
deallocate(self%elements)
endif
end subroutine
subroutine display(vec)
...
end subroutine
type(t_vector) function create_empty_vector()
...
end function
type(t_vector) function create_sized_vector(vec_size)
...
end function
type(t_vector_3) function create_size_3_vector()
...
end function
end module
program main
use m_vector
implicit none
type(t_vector) numbers_none,numbers_some
type(t_vector_3) location
numbers_none=t_vector()
call numbers_none%display()
numbers_some=t_vector(4)
numbers_some%elements(1)=2
call numbers_some%display()
location=t_vector_3()
location%elements(1)=1.0
call location%display()
end program
$ gfortran destructor.f90 -o destructor
$ ./destructor
t_vector:
num_elements= 0
elements=
t_vector:
num_elements= 4
elements=
2.00000000
0.00000000
0.00000000
0.00000000
t_vector_3:
elements=
1.00000000
0.00000000
0.00000000
No allocated check?
What happens if we don’t check that memory is allocated before de-allocating it in our destructor? Lets copy our last code and comment out those lines and see.
$ cp destructor.f90 destructor_no_check.f90 $ nano destructor_no_check.f90
... subroutine destructor_vector(self) implicit none type(t_vector):: self !if(allocated(self%elements)) then deallocate(self%elements) !endif end subroutine subroutine destructor_vector_3(self) implicit none type(t_vector_3):: self !if(allocated(self%elements)) then deallocate(self%elements) !endif end subroutine ...
$ gfortran -g destructor_no_check.f90 -o destructor_no_check $ ./destructor_no_check
What happens and why?
Note: the-g
option provides extra debugging information, such as file and line numbers in the backtrace.Solution
t_vector: num_elements= 0 elements= t_vector: num_elements= 4 elements= 2.00000000 0.00000000 0.00000000 0.00000000 At line 37 of file destructor.f90 Fortran runtime error: Attempt to DEALLOCATE unallocated 'self' Error termination. Backtrace: #0 0x7fd37fc21730 in ??? #1 0x7fd37fc22289 in ??? #2 0x7fd37fc22906 in ??? #3 0x401929 in __m_vector_MOD_destructor_vector at /home/user100/fortran_oop/destructor.f90:37 #4 0x40100d in __m_vector_MOD___final_m_vector_T_vector at /home/user100/fortran_oop/destructor.f90:86 #5 0x401d86 in MAIN__ at /home/user100/fortran_oop/destructor.f90:101 #6 0x401e15 in main at /home/user100/fortran_oop/destructor.f90:89
Key Points
A destructor is used to perform clean up when an object goes out of scope.
To create a destructor use the final keyword when declaring at type bound procedure instead of the procedure keyword.