Consider one of the classic OOP examples (see source code at the end of the post):
Questions:
class(Shape), pointer :: this
as result without ever allocating the pointer. Is this the correct way of defining a constructor for an abstract class in Fortran?Updated with suggestion from Ed Smith which works for non-abstract base classes.
module Shape_mod
implicit none
private
public Shape
type, abstract :: Shape
private
double precision :: centerPoint(2)
contains
procedure :: getCenterPoint
procedure(getArea), deferred :: getArea
end type Shape
interface Shape
module procedure constructor
end interface Shape
abstract interface
function getArea(this) result(area)
import
class(Shape), intent(in) :: this
double precision :: area
end function getArea
end interface
contains
!Correct way of defining a constructor for an abstract class?
function constructor(xCenter, yCenter) result(this)
class(Shape), pointer :: this
double precision, intent(in) :: xCenter
double precision, intent(in) :: yCenter
print *, "constructing base shape"
this%centerPoint = [xCenter, yCenter]
end function constructor
function getCenterPoint(this) result(point)
class(Shape), intent(in) :: this
double precision point(2)
point = this%centerPoint
end function getCenterPoint
end module Shape_mod
module Rectangle_mod
use Shape_mod
implicit none
private
public Rectangle
type, extends(Shape) :: Rectangle
private
double precision :: length
double precision :: width
contains
procedure :: getArea
end type Rectangle
interface Rectangle
module procedure constructor
end interface Rectangle
contains
function constructor(length, width, xCenter, yCenter) result(this)
type(Rectangle), pointer :: this
double precision :: length
double precision :: width
double precision :: xCenter
double precision :: yCenter
print *, "Constructing rectangle"
allocate(this)
this%length = length
this%width = width
!How to invoke the base class constructor here?
!The line below works for non-abstract base classes where the
!constructor result can be type(Shape)
this%Shape = Shape(xCenter, yCenter)
end function constructor
function getArea(this) result(area)
class(Rectangle), intent(in) :: this
double precision :: area
area = this%length * this%width
end function getArea
end module Rectangle_mod
program main
use Rectangle_mod
implicit none
type(Rectangle) :: r
r = Rectangle(4.0d0, 3.0d0, 0.0d0, 2.0d0)
print *, "Rectangle with center point", r%getCenterPoint(), " has area ", r%getArea()
end program main
This program gives the following output:
Constructing rectangle
Rectangle with center point 6.9194863361077724E-310 6.9194863361077724E-310 has area 12.000000000000000
Since the base class constructor haven't been invoked the centerPoint variable isn't initalized. In this simple example the variable could be initialized manually from the Rectangle constructor, but for more complex cases this might lead to significant duplication of code.
This is a good question and I hope someone with more experience of oop in fortran can give a better answer. For your first question, you shouldn't need a pointer, instead you can define the constructor as,
type(Shape) function constructor(xCenter, yCenter)
double precision, intent(in) :: xCenter
double precision, intent(in) :: yCenter
print *, "constructing base shape"
constructor%centerPoint = [xCenter, yCenter]
end function constructor
For your second question, the answer should be to call the parent in the rectangle constructor with the line constructor%Shape = Shape(xCenter, yCenter)
in the rectangle constructor function.
type(Rectangle) function constructor(length, width, xCenter, yCenter)
type(Rectangle), pointer :: this
double precision, intent(in) :: xCenter
double precision, intent(in) :: yCenter
double precision, intent(in) :: length
double precision, intent(in) :: width
print *, "Constructing rectangle"
!invoke the base class constructor here
constructor%Shape_ = Shape(xCenter, yCenter)
constructor%length = length
constructor%width = width
end function constructor
I cannot get this to work with the intel compiler v13.0.1. It returns the error: If the rightmost part-name is of abstract type, data-ref shall be polymorphic
. As I understand it, the fortran 2008 standard should allow you to call the constructor of an abstract type if it is the parent of the current type. This may work in later compilers, check out this answer (and try for your case).
If not, as a minimal working solution of what you want, the solution I eventually used was to have an abstract shape class which defines the interface, and then define the constructor in the fist object which inherits this, here a shape
type (similar to section 11.3.2 of this fortran oop example). The solution is as follows,
module shape_mod
type, abstract :: abstractshape
integer :: color
logical :: filled
integer :: x
integer :: y
end type abstractshape
interface abstractshape
module procedure initShape
end interface abstractshape
type, EXTENDS (abstractshape) :: shape
end type shape
type, EXTENDS (shape) :: rectangle
integer :: length
integer :: width
end type rectangle
interface rectangle
module procedure initRectangle
end interface rectangle
contains
! initialize shape objects
subroutine initShape(this, color, filled, x, y)
class(shape) :: this
integer :: color
logical :: filled
integer :: x
integer :: y
this%color = color
this%filled = filled
this%x = x
this%y = y
end subroutine initShape
! initialize rectangle objects
subroutine initRectangle(this, color, filled, x, y, length, width)
class(rectangle) :: this
integer :: color
logical :: filled
integer :: x
integer :: y
integer, optional :: length
integer, optional :: width
this%shape = shape(color, filled, x, y)
if (present(length)) then
this%length = length
else
this%length = 0
endif
if (present(width)) then
this%width = width
else
this%width = 0
endif
end subroutine initRectangle
end module shape_mod
program test_oop
use shape_mod
implicit none
! declare an instance of rectangle
type(rectangle) :: rect
! calls initRectangle
rect = rectangle(2, .false., 100, 200, 11, 22)
print*, rect%color, rect%filled, rect%x, rect%y, rect%length, rect%width
end program test_oop
Sorry, the notation is slightly different from your example but hopefully this will help...
The "constructor" concept that you are looking for is best achieved by having an "initialise" subroutine that takes a an INTENT([IN] OUT) polymorphic argument with declared type of the abstract parent, as shown in the second part of Ed Smith's answer.
As conceptual background - you cannot create values in Fortran that are of abstract type (that would defeat the meaning of ABSTRACT), but that is exactly what you are trying to do with your constructor function for the parent.
(There is a difference here in creating a value, and then storing that value in some other object. A value of non-abstract type might be stored in a polymorphic object that has a declared type that is abstract and that is a parent type of the type of the value.)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With