考虑以下最小示例:
module lib
private
type node
integer::val
type(node),allocatable::next
end type
type,public::list
integer::num=0
! `first` should be a pointer to avoid making `this` a target in procedures.
type(node),pointer::first=>null(),last=>null()
contains
procedure::add=>list_add
final::list_final
end type
interface list
procedure list_new
end interface
contains
function list_new()result(this)
type(list)::this
print*,'list_new'
allocate(this%first)
this%last=>this%first
end
subroutine list_add(this,val)
class(list),intent(inout)::this
integer,intent(in)::val
print*,'list_add'
this%num=this%num+1
allocate(this%last%next)
this%last=>this%last%next
this%last%val=val
end
subroutine list_final(this)
type(list),intent(inout)::this
print*,'list_final'
! Checking if `first` is associated is necessary to avoid the error on
! finalization of uninitialized instances.
if(associated(this%first))deallocate(this%first)
end
end
program prog
use lib
type(list)::obj ! type(list),allocatable::obj
obj=list() ! allocate(obj,source=list())
call obj%add(42)
end
这是使用可分配节点的链表的简单实现。由于原代码的一些限制,
first
应该是一个指针,在构造函数list_new
中分配。为了避免内存泄漏,当实例超出范围时,最终过程 list_final
将自动释放 first
。但是编译此代码(例如,使用 GNU 编译器 gfortran-13.1.0 使用 -g
标志进行调试)并执行它将在第 Fortran runtime error: Attempting to allocate already allocated variable 'this'
行以 allocate(this%last%next)
结束。在此错误之前,输出是:
list_new
list_final
list_final
list_add
如果我理解正确的话,
list_final
被调用了两次:
type(list)::obj
,obj=list()
后,完成在第obj
行右侧创建的实例。第二个调用释放了
first
的 list()
所指向的内存,并导致 obj%first
指向无效的内存地址,因为指针刚刚在赋值中被复制。所以 last%next
(或 first%next
)似乎已经在 list_add
的第一次调用中分配了。
将
type(list)::obj
替换为 type(list),allocatable::obj
并将 obj=list()
替换为 allocate(obj,source=list())
可以修复此问题,并且永远不会调用 list_final
。
据我所知,这似乎是分配类型实例的唯一安全且干净的方法(无需重新定义赋值并用对析构函数的显式调用替换
final
)。尽管 obj=list()
看起来更优雅并且类似于其他编程语言中的实例初始化表达式,但它可能会在 Fortran 中导致意外行为(并且还会添加额外的复制操作)。
allocate
和 source
是初始化此类类型实例的唯一正确方法吗?
通过定义与类型同名的泛型接口,您可以有效地为派生类型定义具有任意语义的构造函数。正确的 Fortran 编译器将调用该通用接口的特定过程,如果其中一个过程与结构构造函数的参数完全匹配,该结构构造函数在语法上也可以是函数调用,并且如果不匹配,则将结构构造函数解释为结构构造函数.