Fortran中的主持人协会与使用协会

时间:2017-09-13 16:15:33

标签: variables module fortran

是否有任何“一般规则”,哪一个比另一个更好?

这个问题的背景是:我昨天问了一个关于主持人协会的另一个问题(link),在评论中,我被建议谨慎使用主持人协会。原因是通过主机关联,很容易无意中修改变量,因为子例程可以无限制地访问模块中声明的所有变量。

为了说明这一点,我将使用以下代码示例:

module mod
implicit none

real :: x

contains

subroutine sub(y)
  use other_mod, only: a

  real, intent(out) :: y

  y = a + x
  a = a + 1.
  x = x + 1.
end subroutine sub

end module mod

a中修改了xsub。但是对于x,我需要通过所有代码才能看到这一点。通过查看a的声明部分,可以很容易地看到sub中使用sub(并且可能已修改)。

从这个意义上说,似乎最好有两种模块:

  1. 仅包含变量声明的模块或模块(在需要时使用
  2. 仅包含过程和可能的参数声明但没有变量声明的模块
  3. 这完全消除了变量的主机关联。

    但出于多种原因,这似乎并不实用:

    • 我可能有十几个子程序在一个模块中使用(和修改)相同的变量。每次使用这些变量都会使代码混乱,特别是如果有很多代码(比如几百个)。
    • 从实际使用的位置分离变量的声明似乎使代码不易理解:
      • 要么创建一个包含所有声明的巨型控制文件。如果代码很大并且使用了很多变量,这可能会非常混乱。
      • 或者,为每个模块(或模块组,如果它们依赖于相同的内容)创建单独的控制文件。这将使代码本身更易于理解,因为使用变量会立即显示它们的来源。但它会使代码的结构复杂化,从而创建一个更加复杂的文件结构(以及相应的依赖结构)。

    最后,所有这些归结为:何时将变量声明放在使用它们的同一模块中(以便它们被主机关联使用)以及何时更为明智将声明外包给一个单独的模块更为明智(这样变量将在需要时通过使用关联来使用)?

    是否有任何一般性指导方针或是否应根据具体情况决定?如果是个案,那么为什么选择另一个呢?

1 个答案:

答案 0 :(得分:2)

Fortran提供了几种在不同“程序单元”之间创建,存储,使用和传递数据的方法:主程序,外部程序和模块。 1 As您知道,每个程序单元都可以包含内部过程 - 通过主机关联,可以访问主机中包含的任何变量或过程。这通常被视为一种优势。正如@HighPerformanceMark在评论中所提到的,何时使用主机关联或使用关联的一般准则是:

  

当变量仅被(或主要)在同一模块中声明的例程使用时使用主机关联,并且当您想要定义要在许多模块中使用的变量时使用use-association

从你的评论中,听起来主程序中的大多数或所有主变量都是由每个内部程序(大约十几个子程序)访问的。如果是这种情况,那么host-association似乎是一个非常合理的选择,并且实际上没有必要明确地将参数传递给每个子例程。另一方面,如果每个子例程实际上只使用了变量的子集,那么对它进行更明确的描述可能是合理的。

和你一样,我对在过程中使用尚未在参数列表中声明的变量感到不舒服。这部分是因为我喜欢args列表是如何自我记录的,它帮助我推断代码以及如何在其中操作数据。与其他工作人员合作时更是如此,或者如果我花了一些时间远离代码并且我对它的记忆已经消失了。但是,我发现没有理由完全避免主机关联,只要你知道它是如何工作的并且有策略。

事实上,我倾向于经常使用内部过程和主机关联,特别是对于短函数/子例程。我发现松散地将主机视为“对象”,将变量视为“属性”以及任何内部过程非常类似于对象的“方法”来完成工作。当然,这简化了事情,但这确实是重点。

对于更复杂的程序,我减少了“主”程序本身的主机关联量,然后主要用于以正确的顺序和上下文调用各种子程序。在这种情况下,我们可以利用use-association并直接在需要它们的程序单元中选择use模块实体(例如过程,变量,类型,参数)。我们可以进一步限制对only:所需的模块实体的访问。这有助于提高可读性,数据流清晰显示,我发现稍后更新代码更为直接。你知道,继承,封装和诸如此类......但Fortran风格。这实际上非常好。

这是一个示例程序结构,适用于我和我在Fortran中工作的中等大小的项目。我喜欢将我广泛使用的(静态)参数保存在一个单独的模块中(如果根据功能分组,则保留模块)。我将派生类型和类型绑定过程保存在另一个单独的模块中。如果它有用,我会创建某些模块实体private,这样它们就无法从其他程序单元访问。我猜就是这样。

module params
    implicit none
    public              !! All items public/accessible by default.
    integer, parameter  :: dp = kind(0.d0)
    integer, parameter  :: nrows = 3
    real(dp), parameter :: one=1.0_dp, two=2.0_dp
    ...
end module params

module types
    use params, only: dp, nrows
    implicit none
    public              !! Public by default.
    private :: dim2
    ...
    integer, parameter :: dim2 = 3
    ...
    type :: A
        integer :: id
        real(dp), dimension(nrows,dim2) :: data
        contains
        procedure, pass :: init
    end type A
    ...
    contains
    subroutine init(self, ...)
        ...
    end subroutine init
    ...
end module types

module utils
    implicit none
    private             !! Private by default.
    public :: workSub1, workSub2, subErr
    ...
    integer,save :: count=0 !! Accessible only to entities in this module.
    ...
    contains
    subroutine workSub1(...)
        ...
    end subroutine workSub1

    subroutine workSub2(...)
        ...
    end subroutine workSub2

    subroutine subErr(...)
        ...
    end subroutine subErr

end module utils


program main
    !! An example program structure.
    use params, only: dp
    implicit none
    real(dp) :: xvar, yvar, zvar
    integer  :: n, i
    logical  :: rc

    call execute_work_subroutines()

    contains                !! Internal procs inherit all vars declared or USEd.
    subroutine execute_work_subroutines()
        use types, only: A
        type(A) :: DataSet
        !! begin
        call DataSet%init(i)
        do i = 1,n
            call workSub1(xvar,yvar,zvar,A,i,rc)
            if (rc) call subErr(rc)
            call workSub2(A,rc)
            if (rc) call subErr(rc)
        enddo
    end subroutine execute_work_subroutines
end program main

1 还有子模块,但我不熟悉它们,也不想提供误导性信息。它们似乎对逻辑上分离大型模块很有用。