Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Invoking constructor of abstract base class in Fortran

Tags:

oop

fortran

Consider one of the classic OOP examples (see source code at the end of the post):

  • Abstract base class Shape
  • Class Rectangle extending Shape

Questions:

  1. In the source code below I've tried to define a constructor for the abstract class Shape using 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?
  2. How can I invoke the constructor of the base class (Shape) in the constructor of the extending class (Rectangle)?

Example Source Code

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.

like image 706
Paul Avatar asked Jun 13 '15 21:06

Paul


2 Answers

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

like image 112
Ed Smith Avatar answered Oct 21 '22 05:10

Ed Smith


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

like image 31
IanH Avatar answered Oct 21 '22 03:10

IanH