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.

+3


source to share


2 answers


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 ...

+2


source


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.)

+1


source







All Articles