Derived types
Overview
Teaching: 10 min
Exercises: 20 minQuestions
How are data structures (derived types) defined in Fortran?
How can we control access to components of data structures in Fortran?
How does assignment between derived types work?
Objectives
Be able to declare your own data structures
Understand assignment operations in derived types and when shallow/deep copying occurs
Derived types provide the fundamental basis for data structures (cf. struct
in
C++). Derived types are sometimes referred to as user-defined types. The
advice given in the introductory course was always to place type
definitions in a module.
Definition
We recall that the general form of the declaration is:
type [ [, attribute-list] :: ] type-name
[private]
component-part
[ contains
procedure-part ]
end type [ type-name ]
Components of types may be intrinsic, allocatable, pointer, or other derived
types. The procedure part is used (optionally) to define so-called
type-bound procedures. These are the equivalent of class methods in other
languages (and will be covered in detail in later Sections). Scope of
components may be controlled via public
or private
statements.
Encapsulation may be achieved by declaring a public
type with private
components, e.g.,
type, public :: my_opaque_t
private
integer :: idata
end type my_opaque_t
It is also possible to have a mixture, e.g.:
type, public :: my_semi_opaque_t
private
integer :: idata ! default private
integer, public :: ndata ! explicitly public
end type my_semi_opaque_t
Components are referenced using the component selector %
. Types with
components which are themselves derived types give rise to the idea of
an ultimate component, which is an intrinsic type for which no further
application of %
is relevant.
protected
attributeNote that there is a
protected
attribute in Fortran, although it has a slightly different usage than in C++. It will be discussed later as part of the section on submodules. Some authors argue thatprotected
should be avoided as it represents a breakdown of encapsulation.
Assignments and copying
Intrinsic assignments
Intrinsic assignment is available for types, and just involves an intrinsic assignment of each component in turn. Schemetically:
type (my_semi_opaque_t) :: a
type (my_semi_opaque_t) :: b
b = my_semi_opaque(2, 3)
a = b
Here, the components of a
will take on the values of the components of b
.
If a type component is itself a derived type, then intrinsic assignment takes place in the same way for that component.
Example (5 minutes)
Intrinsic assignment
The accompanying module
my_semi_opaque_type.f90
provides a public definition of the type as defined above, and includes a function to initialise the two components. The accompanying programexercise1.f90
will make the intrinsic assignment. You can compile with, e.g.,$ ftn my_semi_opaque_type.f90 example1.f90
How are you going to check that the result of the assignment is correct for both components (by printing the result to the screen)? Write some code to perform this check.
Solution
subroutine my_semi_opaque_print(name, val) character(len=*), intent(in) :: name type(my_semi_opaque_t), intent(in) :: val print *, name, "%idata=", val%idata print *, name, "%ndata=", val%ndata end subroutine my_semi_opaque_print
This code could be added to either the example program or the module, from a code organisation perspective the module probably makes the most sense. You should obtain the following output
gfortran my_semi_opaque_type.f90 example1.f90 && ./a.out a%idata= 2 a%ndata= 3
Allocatable components
Suppose our derived type had an allocatable component. For example:
type, public :: my_array_t
integer :: nlen
real, allocatable :: values(:)
end type my_array_t
Intrinsic assignment takes place for such an object in much the same way, e.g.,
type (my_array_t) :: a
type (my_array_t) :: b
! ... establish some data for b ...
a = b
However, there are a number of extra steps involved: 1) if the values
component of a
on the left hand side is already allocated, it is
first deallocated; 2) an appropriate allocation is then made for a
depending on the size and bounds of b%values(:)
; 3) all the relevant
values of the right-hand side are copied to the left-hand side.
If b%values
is unallocated, then step (2) is omitted, and a%values
is also unallocated after assignment.
This is, in the jargon, a deep copy. You have a copy of the actual data.
Again, this process is repeated until any ultimate allocatable component is reached.
Derived types as procedure arguments
Recall the behaviour of allocatable arrays as arguments to procedures. When the argument is
intent(out)
the array is deallocated, the same applies to allocatable components of derived types when a derived-type argument isintent(out)
.
Pointer components
For types with pointer components, the situation is different. Consider:
type, public :: my_array_pointer_t
integer :: nlen
real, pointer :: values(:)
end type my_array_pointer_t
Assignment here means that the pointer becomes associated with the target on the right-hand side.
type (my_array_pointer_t) :: a
type (my_array_pointer_t) :: b
b%nlen = 10
allocate(b%values(b%nlen))
a = b
This implies that if, in a subsequent step, the target becomes
unassociated (or deallocated), then the reference retained in
a
is in a bad state.
This is a shallow copy. No data have been duplicated; only the pointer description itself.
Example (5 minutes)
Shallow and deep copies
In the accompanying module
my_array_type.f90
both the types above have been declared, along with a function to initialise some array values. Compile the example program:$ ftn my_array_type.f90 example2.f90
and check the values printed out. What happens if you insert a call to
my_array_destroy(a)
(which deallocates the values associated with themy_array_t
argument) at the end of the program and try to print the values of the pointer typec
again?Solution 1A
The output of the initial program
State of a 3 T State of c 3 T 1.00000000 2.00000000 3.00000000
Solution 1B
The output of the program after destroying
a
showsc%values
now points to uninitialised data, howeverc%nlen
is a deep copy and is still “valid” (in some sense).State of a 3 T State of c 3 T 1.00000000 2.00000000 3.00000000 State of c 3 T -1.40913533E-36 1.56146688E-41 1.12103877E-44
What happens if you try to make a direct assignment between a
my_array_t
object on the right-hand side, and amy_array_pointer_t
on the left-hand side?Solution 2
The compiler rejects the code as invalid due to different types.
gfortran my_array_type.f90 example2.f90 && ./a.out example2.f90:24:6: 24 | c = b | 1 Error: Cannot convert TYPE(my_array_t) to TYPE(my_array_pointer_t) at (1)
A defined assignment
If something other than intrinsic assignment for types is required, it is
possible to overload the meaning of the assignment operation =
.
For example, if an assignment between two objects of my_array_t
were
required, one could write, as part of a module:
subroutine my_assignment(a, b)
type (my_array_t), intent(out) :: a
type (my_array_t), intent(in) :: b
a%nlen = b%nlen
a%values = b%values
end subroutine my_assignment
interface assignment (=)
module procedure my_assignment
end interface assignment (=)
This must be a subroutine with two arguments, the first with intent(out)
(or inout
) to represent the left-hand side of the assignment, and the
second with intent(in)
to represent the right-hand side.
Exercise (10 minutes)
Constructing a derived type pointer
In
example2.f90
we explcitly assigned both components of themy_array_pointer_t
in the code and found we could not make an assignment betweenmy_array_pointer_t
andmy_array_t
. Add a subroutine inmy_array_type.f90
to make this possible.Solution
First we define the
(=)
interface in the moduleinterface assignment (=) module procedure my_array_ptr_assignment end interface assignment (=)
and the assignemnt subroutine itself
subroutine my_array_ptr_assignment(a, b) type(my_array_pointer_t), intent(out) :: a type(my_array_t), pointer, intent(in) :: b a%nlen = b%nlen a%values => b%values end subroutine my_array_ptr_assignment
noting that the assignment target must be a
pointer
so that we can use it as the target of themy_array_pointer_t
. Then we can usec = a
in the example program in place of the member-wise assignment.
Key Points
Derived types enable creating custom data structures in Fortran.
Access to components of derived types can be controlled via the
private
attribute