【问题标题】:Invoking constructor of abstract base class in Fortran在 Fortran 中调用抽象基类的构造函数
【发布时间】:2015-08-29 16:26:03
【问题描述】:

考虑一个经典的 OOP 示例(参见文章末尾的源代码):

  • 抽象基类 Shape
  • 类 Rectangle 扩展 Shape

问题:

  1. 在下面的源代码中,我尝试使用class(Shape), pointer :: this 为抽象类Shape 定义一个构造函数,结果从未分配指针。这是在 Fortran 中为抽象类定义构造函数的正确方法吗?
  2. 如何在扩展类(Rectangle)的构造函数中调用基类(Shape)的构造函数?

示例源代码

更新了来自Ed Smith 的建议,该建议适用于非抽象基类。

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

这个程序给出以下输出:

 Constructing rectangle
 Rectangle with center point   6.9194863361077724E-310   6.9194863361077724E-310  has area    12.000000000000000 

由于尚未调用基类构造函数,因此 centerPoint 变量未初始化。在这个简单的示例中,可以从 Rectangle 构造函数手动初始化变量,但对于更复杂的情况,这可能会导致大量代码重复。

【问题讨论】:

    标签: oop fortran


    【解决方案1】:

    这是一个很好的问题,希望有更多fortran oop 经验的人能给出更好的答案。对于您的第一个问题,您不需要指针,而是可以将构造函数定义为,

    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
    

    对于第二个问题,答案应该是使用矩形构造函数中的constructor%Shape = Shape(xCenter, yCenter) 行调用矩形构造函数中的父级。

    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
    

    我无法让它与英特尔编译器 v13.0.1 一起使用。它返回错误:If the rightmost part-name is of abstract type, data-ref shall be polymorphic。据我了解,fortran 2008 标准应该允许您调用抽象类型的构造函数,如果它是当前类型的父类。这可能在以后的编译器中有效,请查看this answer(并根据您的情况尝试)。

    如果不是,作为你想要的最小工作解决方案,我最终使用的解决方案是有一个定义接口的抽象形状类,然后在继承它的第一个对象中定义构造函数,这里是 @987654327 @type(类似于thisfortran oop 示例的第 11.3.2 节)。解决方法如下,

    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
    

    抱歉,符号与您的示例略有不同,但希望这会有所帮助...

    【讨论】:

    • type(Shape) 对抽象类型的 Shape 无效。也许尝试class(Shape) 并使结果变量可分配。 [这是否有助于回答我不知道的问题...]
    • 好建议。我不知道this%Shape-syntax。如果我将this%Shape = Shape(xCenter, yCenter) 添加到 Rectangle 构造函数,它适用于非抽象基类。对于抽象类,构造函数似乎必须具有返回类型class(Shape), pointerclass(Shape), allocatable。在这种情况下,gfortran 给我一个错误:Error: Can't convert CLASS(shape) to TYPE(shape) at (1).
    • 编译器对上面显示的代码的抱怨是正确的 - 错误消息是 F2008 中 C611 的简单结果。该限制是适用的,因为从概念上讲,您不能拥有动态类型为抽象的对象,但这正是 object % abstract_parent_type 所要求的语法。
    【解决方案2】:

    您正在寻找的“构造函数”概念最好通过具有一个“初始化”子例程来实现,该子例程接受一个声明类型为抽象父级的 INTENT([IN] OUT) 多态参数,如第二部分所示Ed Smith 的回答。

    作为概念背景 - 您不能在 Fortran 中创建抽象类型的值(这会破坏 ABSTRACT 的含义),但这正是您试图对父级的构造函数执行的操作。

    (这里有一个区别是创建一个值,然后将该值存储在某个其他对象中。非抽象类型的值可能存储在一个多态对象中,该对象具有声明的抽象类型,即值的类型的父类型。)

    【讨论】:

    • 嗨@IanH,能够为抽象类中的构造函数设置接口和最低要求会很有用。在其他 oop 语言中允许使用抽象构造函数,例如java (stackoverflow.com/questions/260666/…)、c# (stackoverflow.com/questions/5601777/…)。是否有一种语法形式可以直接在 Fortran 中执行此操作,例如 allocate(this, source=this%abstact_parent)
    • 根据您的链接示例,我认为您的意思是编写“抽象类的构造函数 ”。您不能通过当前 Fortran 中的过程强制定义父组件(或通过过程定义任何对象,就此而言),但除此之外,抽象类的“构造函数”的接口只是过程的接口抽象类的作者提供的。如果抽象类型作者提供了一个接受两个参数的“构造函数”子例程,那么用户就不能用三个参数调用它......
    猜你喜欢
    • 2017-08-16
    • 2014-08-09
    • 1970-01-01
    • 1970-01-01
    • 2018-09-26
    • 1970-01-01
    • 2011-03-21
    • 2010-09-21
    • 2019-04-19
    相关资源
    最近更新 更多