如何在Fortran中覆盖结构体构造函数

14

目前是否可以在Fortran中覆盖结构构造函数?我已经看到过像这样的示例(例如在Fortran 2003规范中):

module mymod

  type mytype
    integer :: x
    ! Other stuff
  end type

  interface mytype
    module procedure init_mytype
  end interface

contains
  type(mytype) function init_mytype(i)
    integer, intent(in) :: i
    if(i > 0) then
      init_mytype%x = 1
    else
      init_mytype%x = 2
    end if
  end function
end

program test
  use mymod
  type(mytype) :: x
  x = mytype(0)
end program

由于变量名冲突(例如,错误:DERIVED属性“mytype”与PROCEDURE属性发生冲突(1)),这基本上会生成一堆错误。 Fortran 2003示例的逐字复制会生成类似的错误。我在gfortran 4.4、ifort 10.1和11.1中尝试过,它们都会产生相同的错误。

我的问题是:这只是Fortran 2003未实现的功能吗?还是我实现方法有误?

编辑:我遇到了缺陷报告和关于此问题的公告补丁。然而,我尝试使用11月版本的gcc46,并没有成功,仍然出现类似的错误。

编辑2:使用Intel Fortran 12.1.0似乎可以解决上述代码的问题。

2个回答

19

目前无法在Fortran中覆盖结构构造函数。即使使用您的方法,也完全不涉及构造函数重写。主要原因是结构构造函数与面向对象编程(OOP)构造函数有一些相似之处,但这只是另一个想法。

您无法在初始化表达式中使用非内置函数。您只能使用常量、数组或结构构造函数、内置函数等。有关更多信息,请查看Fortran 2003草案中的7.1.7初始化表达式。

考虑到这一事实,我完全不理解以下内容的真正区别:

type(mytype) :: x
x = mytype(0)

并且

type(mytype) :: x
x = init_mytype(0)

我需要在mymod模块中使用INTERFACE块,这有什么意义呢?

说实话,这两种方法是有区别的,而且区别很大——第一种方法是误导性的。这个函数不是构造函数(因为Fortran中根本没有面向对象的构造函数),它是一个初始化器。


在主流的面向对象编程中,构造函数负责顺序执行两件事:

  1. 内存分配。
  2. 成员初始化。

让我们来看一些不同语言中实例化类的示例。

Java中:

MyType mt = new MyType(1);

一个非常重要的事实被隐藏了 - 对象实际上是指向类类型变量的指针。在C++中,等价于使用堆上分配
MyType* mt = new MyType(1);

但是在两种语言中,我们可以看到即使在语法层面也反映了两个构造函数的职责。它由两部分组成:关键字new(分配)和构造函数名称(初始化)。在Objective-C语法中,这一事实甚至更加强调:

MyType* mt = [[MyType alloc] init:1];

然而,有时你会看到其他形式的构造函数调用。在C++中,如果是在栈上分配内存,则使用特殊(非常糟糕)的语法结构。

MyType mt(1);

这实际上是如此误导性,以至于我们不能考虑它。

Python

mt = MyType(1)

事实上,对象实际上是一个指针和分配发生在首位这两个事实都被隐藏了(在语法层面上)。而这个方法被称为...__init__!太具有误导性了。与之相比,C ++的堆栈分配就显得逊色了。=)


无论如何,语言中有“构造函数”的想法都意味着使用某种特殊的方法可以在一条语句中执行“分配和初始化”,如果您认为这是“真正的OOP”方式,我有一个不好的消息。即使Smalltalk 没有构造函数。它只是在类本身上有一个new方法的约定(它们是元类的单例对象)。工厂设计模式用于许多其他语言来实现相同的目标。
我曾经读过Fortran中模块概念的灵感来自Modula-2。对我来说,面向对象的特性似乎受到了Oberon-2的启发。Oberon-2中也没有构造函数。但是当然有纯分配和预声明的NEW过程(像Fortran中的ALLOCATE,但ALLOCATE是语句)。分配后,您可以(在实践中应该)调用某个初始化程序,这只是普通的方法。没有什么特别之处。
因此,您可以使用某种类型的工厂来初始化对象。这实际上就是使用模块而不是单例对象所做的事情。或者更好地说,他们(Java/C#/ ...程序员)使用单例对象方法而不是普通函数,因为没有后者(没有模块-没有办法有普通函数,只有方法)。

您还可以使用类型限定的SUBROUTINE。

MODULE mymod

  TYPE mytype
    PRIVATE
    INTEGER :: x
    CONTAINS
    PROCEDURE, PASS :: init
  END TYPE

CONTAINS

  SUBROUTINE init(this, i)
    CLASS(mytype), INTENT(OUT) :: this
    INTEGER, INTENT(IN) :: i

    IF(i > 0) THEN
      this%x = 1
    ELSE
      this%x = 2
    END IF
  END SUBROUTINE init

END

PROGRAM test

  USE mymod

  TYPE(mytype) :: x

  CALL x%init(1)

END PROGRAM

init子程序中this参数的INTENT(OUT)看起来没问题。因为我们希望这个方法只被调用一次,而且是在分配之后立即调用。也许控制这个假设不会出错是个好主意。可以向mytype添加一个布尔标志LOGICAL :: inited,检查它是否为.false.,并在第一次初始化时将其设置为.true.,以及在尝试重新初始化时执行其他操作。我肯定记得Google Groups上有一些关于这方面的讨论...但我找不到它了。


2
谢谢您的澄清。我明白这不是纯粹面向对象编程意义上的构造函数,而且类似的替代方法可以实现大部分相同的功能。相反,这是来自p445(C.1.6)的几乎逐字的示例,作者选择在规范工作草案中称之为“结构构造函数”。我想知道在Fortran的常见实现中是否可能进行这样的覆盖。但我会牢记您的建议,再次感谢您。 - marshall.ward
阅读构造函数只执行分配和初始化这两项任务有些奇怪。事实上,构造函数最常见的应用程序是资源获取(https://en.wikipedia.org/wiki/Resource_acquisition_is_initialization)。将对象的创建和初始化合并在一起的好处是,由于线程可能在两者之间被杀死,因此可能会导致资源泄漏或悬空指针。 - Vladimir Nikishkin

6
我查阅了Fortran 2008标准,它允许您定义一个与派生类型同名的通用接口。但是,我的编译器(Intel Fortran 11.1)无法编译该代码,因此我怀疑(没有Fortran 2003标准副本可供参考)这是Fortran 2003标准中尚未实现的特性。
除此之外,您的程序存在错误。您的函数声明:
  type(mytype) function init_mytype
    integer, intent(in) :: i

指定一个在函数规范中不存在但有意义的参数,可能需要将其重写为:

  type(mytype) function init_mytype(i)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接