Calling the constructor of an abstract base class in Fortran
Consider one of the classic examples of OOP (see the source code at the end of the post):
- Base class abstract form
- Rectangle class extending the shape
Questions:
- In the source code below, I tried to define a constructor for an abstract Shape class using
class(Shape), pointer :: this
as a result without allocating a pointer. Is this the correct way to define a constructor for an abstract class in Fortran? - How can I call the base class constructor (Shape) in the extending class constructor (Rectangle)?
Sample source code
Updated with Ed Smith's suggestion that 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
Because the base class constructor was not called, the centerPoint variable is not initialized. In this simple example, a variable can be manually initialized from the Rectangle constructor, but for more complex cases this can lead to significant code duplication.
source to share
This is a good question and I hope someone with more fortran experience can provide a better answer. For your first question, you don't need a pointer, you can instead 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 a 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 v13.0.1 compiler. It returns an 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 this answer (and try for your case).
If not, then as a minimal working solution to what you want, the solution I ended up using was to have an abstract form class that defines an interface, and then define a constructor on the first object that inherits that. here a shape
(similar to section 11.3.2 this one for 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 helps ...
source to share
The "constructor" concept you are looking for is best achieved by "initializing" a subroutine that takes an INTENT ([IN] OUT) polymorphic argument with the declared type of the abstract parent, as shown in the second part of Ed Smith's answer.
As a conceptual background - you cannot create values ββin Fortran that are of abstract type (to defeat the meaning of ABSTRACT), but that is exactly what you are trying to do with your parent constructor function.
(There is a difference here in creating a value and then storing that value in some other object. A value of a non-abstract type can be stored in a polymorphic object with a declared type that is abstract, and that is the parent type of the value type.)
source to share