派生类型的构造函数

Constructor of derived types

我正在尝试为 derived 类型的抽象类 solve this other question 编写一个构造函数,但它似乎不起作用,或者更好的是,它是'根本没有打电话。

目的是让运行时多态性设置正确的动物腿数。

这是两个模块:

动物

module animal_module
    implicit none

    type, abstract :: animal
        private
        integer, public :: nlegs = -1
    contains
        procedure :: legs
    end type animal

contains

    function legs(this) result(n)
        class(animal), intent(in) :: this
        integer :: n

        n = this%nlegs
    end function legs

module cat_module
    use animal_module, only : animal
    implicit none

    type, extends(animal) :: cat
        private
    contains
        procedure :: setlegs => setlegs
    end type cat

    interface cat
        module procedure init_cat
    end interface cat

contains

    type(cat) function init_cat(this)
        class(cat), intent(inout) :: this
        print *, "Cat!"
        this%nlegs = -4
    end function init_cat

主程序

program oo
    use animal_module
    use cat_module
    implicit none

    type(cat) :: c
    type(bee) :: b

    character(len = 3) :: what = "cat"

    class(animal), allocatable :: q

    select case(what)
    case("cat")
        print *, "you will see a cat"
        allocate(cat :: q)
        q = cat() ! <----- this line does not change anything

    case default
        print *, "ohnoes, nothing is prepared!"
        stop 1
    end select

    print *, "this animal has ", q%legs(), " legs."
    print *, "cat  animal has ", c%legs(), " legs."
end program

根本没有调用构造函数,腿数还是-1

cat 类型可用的非默认构造函数由模块过程 init_cat 给出。你定义的这个函数像

type(cat) function init_cat(this)
    class(cat), intent(inout) :: this
end function init_cat

它是一个单参数函数,class(cat)。在您以后的参考中

q = cat()

泛型 cat 下没有与该引用匹配的特定函数:函数 init_cat 不接受无参数引用。改为使用默认结构构造函数。

您必须以匹配您的 init_cat 接口的方式引用通用 cat 才能调用该特定函数。

您想将 init_cat 函数更改为

type(cat) function init_cat()
    ! print*, "Making a cat"
    init_cat%nlegs = -4
end function init_cat

然后你可以根据需要引用q=cat()

请注意,在最初,您正试图 "construct" 一个 cat 实例,但您并未将此构造的实体作为函数结果返回。相反,您正在修改一个参数(已经构造)。结构构造函数旨在用于返回这些有用的东西。

另请注意,您不需要

allocate (cat :: q)
q = cat()

q 的固有分配已经处理了 q 的分配。

FWIW,这里是一些比较三种方法的示例代码(方法 = 1:来源分配,2:多态分配,3:混合方法)。

module animal_module
    implicit none

    type, abstract :: animal_t
        integer :: nlegs = -1
    contains
        procedure :: legs   !! defines a binding to some procedure
    endtype

contains
    function legs(this) result(n)
        class(animal_t), intent(in) :: this
            !! The passed variable needs to be declared as "class"
            !! to use this routine as a type-bound procedure (TBP).
        integer :: n
        n = this % nlegs
    end
end

module cat_module
    use animal_module, only : animal_t
    implicit none

    type, extends(animal_t) :: cat_t
    endtype

    interface cat_t   !! overloads the definition of cat_t() (as a procedure)
        module procedure make_cat
    end interface

contains
    function make_cat() result( ret )   !! a usual function
        type(cat_t) :: ret   !<-- returns a concrete-type object
        ret % nlegs = -4
    end
end

program main
    use cat_module, only: cat_t, animal_t
    implicit none
    integer :: method

    type(cat_t) :: c
    class(animal_t), allocatable :: q

    print *, "How to create a cat? [method = 1,2,3]"
    read *, method

    select case ( method )
        case ( 1 )
            print *, "1: sourced allocation"

            allocate( q, source = cat_t() )

            !! An object created by a function "cat_t()" is used to
            !! allocate "q" with the type and value taken from source=.
            !! (Empirically most stable for different compilers/versions.)

        case ( 2 )
            print *, "2: polymorphic assignment"

            q = cat_t()

            !! Similar to sourced allocation. "q" is automatically allocated.
            !! (Note: Old compilers may have bugs, so tests are recommended...)

        case ( 3 )
            print *, "3: mixed approach"

            allocate( cat_t :: q )
            q = cat_t()

            !! First allocate "q" with a concrete type "cat_t"
            !! and then assign a value obtained from cat_t().

        case default ; stop "unknown method"
    endselect

    c = cat_t()
    !! "c" is just a concrete-type variable (not "allocatable")
    !! and assigned with a value obtained from cat_t().

    print *, "c % legs() = ", c % legs()
    print *, "q % legs() = ", q % legs()
end

--------------------------------------------------
Test

$ gfortran test.f90   # using version 8 or 9

$ echo 1 | ./a.out
 How to create a cat? [method = 1,2,3]
 1: sourced allocation
 c % legs() =           -4
 q % legs() =           -4

$ echo 2 | ./a.out
 How to create a cat? [method = 1,2,3]
 2: polymorphic assignment
 c % legs() =           -4
 q % legs() =           -4

$ echo 3 | ./a.out
 How to create a cat? [method = 1,2,3]
 3: mixed approach
 c % legs() =           -4
 q % legs() =           -4

--------------------------------------------------
Side notes

* It is also OK to directly use make_cat() to generate a value of cat_t:
  e.g., allocate( q, source = make_cat() ) or q = make_cat().
  In this case, we do not need to overload cat_t() via interface.

* Another approach is to write an "initializer" as a type-bound procedure,
  and call it explicitly as q % init() (after allocating it via
  allocate( cat_t :: q )). If the type contains pointer components,
  this approach may be more straightforward by avoiding copy of
  components (which can be problematic for pointer components).