是否有任何“一般规则”,哪一个比另一个更好?
这个问题的背景是:我昨天问了一个关于主持人协会的另一个问题(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
中修改了x
和sub
。但是对于x
,我需要通过所有代码才能看到这一点。通过查看a
的声明部分,可以很容易地看到sub
中使用sub
(并且可能已修改)。
从这个意义上说,似乎最好有两种模块:
这完全消除了变量的主机关联。
但出于多种原因,这似乎并不实用:
最后,所有这些归结为:何时将变量声明放在使用它们的同一模块中(以便它们被主机关联使用)以及何时更为明智将声明外包给一个单独的模块更为明智(这样变量将在需要时通过使用关联来使用)?
是否有任何一般性指导方针或是否应根据具体情况决定?如果是个案,那么为什么选择另一个呢?
答案 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 还有子模块,但我不熟悉它们,也不想提供误导性信息。它们似乎对逻辑上分离大型模块很有用。