LoginSignup
2
2

More than 5 years have passed since last update.

続 Fortran のオブジェクトの寿命などについて

Posted at

前記事
https://qiita.com/cure_honey/items/aa477bdf364a0604d416

ユーザー定義コンストラクタ

派生型変数に単に値を与える場合ならば、デフォルトのコンストラクタで十分ですが、代入時に値チェックなどをしようとすると、ユーザー定義のコンストラクタを書く必要が出てきます。これを実現する方法としては、初期化用の関数を定義して、interface によって、この関数でデフォルトコンストラクタを上書きする方法がとられます。

しかし、この方法には欠点があって、右辺で関数の返り値として生成されるオブジェクトは、左辺にディープ・コピーによって代入され、直後に解放される一時オブジェクトであり、代入直後に解放されるという無駄な処理がなされます。

本来、関数の返り値として生成されたオブジェクトの番地だけを渡せばいいのですが、Fortran の allocatable は、原則ディープ・コピーされるので無駄が生じます。allocatable 変数の参照番地は move_alloc サブルーチンで move することが出来ますが、関数の返り値を引数に取ることは出来ないようです。 

参照番地のコピーはポインタを使えば可能で、関数の返り値の属性を allocatable ではなく pointer にすれば、この問題は解決しますが pointer は最適化を妨げ、解放処理を人間側が明示的に行わねばならないので、できれば使いたくありません。

assignment 演算子 (=) のユーザー定義上書きによる方法

operator overload を使って、代入をユーザー定義のサブルーチンで上書きする方法があります。これは Fortran 90 によって導入された機能で、代入演算子の両辺の型とそれぞれ一致する型を持つ引数のサブルーチンを呼び出させられます。ただしこれは、コンパイル・リンク時の静的に解決です。

この機能は Fortran 90 での導入なので、Fortran 95 で導入された elemental 属性による引数の rank によらない記述や、Fortran 2003 で導入されたいわゆる継承の扱いがうまくできないという問題があります。(Fortran 2008 で assumed rank という配列の rank によらない仮引数の仕組みが導入されましたが、あくまで C 言語との相互運用用で Fortran 内では使いようがない感じです。)

プログラム例

ここでは有理数(分数)を念頭に、2つの整数変数を要素成分にもつ派生型を定義して、その分母にあたる成分に0を代入しようとすると警告文を表示する例を考えます。

またファイナライザを定義することで、オブジェクト解放のタイミングを観察します。

第1の方法として、初期化関数をデフォルト・コンストラクタに上書きする方法を、第2の方法として、初期化サブルーチンを assignment 演算子に上書きする方法を見てみます。

第一の方法では、elemental 属性をつけることで rank に依らず、静的か allocatable かにもよらず、また左辺の(継承でゆるされる)型に依らないコンストラクタを定義出来ます。しかし、無駄なオブジェクト生成・解放が行われます。

第二の方法では、スカラーか配列かによってそれぞれ用のサブルーチンを用意する必要があります。また静的に型が固定されているので、代入の左右両辺の型毎にもサブルーチンを用意する必要があります。用意されていない組み合わせには、デフォルト・コンストラクタが適用されます。また左辺の変数が静的か allocatable かを区別する処理も必要になります。

    module m_test
        implicit none
        interface t_rat1
            procedure :: init_rat1       
        end interface

        interface assignment (=)
            procedure :: init_rat2_alloc, init_rat2_alloc_arr
        end interface

        type :: t_rat1
            integer :: inume = 0, ideno = 1
        contains
            final :: fin_rat1
        end type t_rat1

        type :: t_rat2
            integer :: inume = 0, ideno = 1
        contains
            final :: fin_rat2
        end type t_rat2

    contains    

        impure elemental function init_rat1(inume, ideno) result(res)
            integer, intent(in) :: inume, ideno
            type (t_rat1) :: res
            if (ideno == 0) print *, '**************!!denominator is 0!!**************'
            res%inume = inume
            res%ideno = ideno
        end function init_rat1    

        subroutine fin_rat1(this)
            type(t_rat1), intent(in out) :: this
            print *, '..fin_rat1', this%inume, this%ideno
        end subroutine fin_rat1

        subroutine init_rat2_alloc(b, a) 
            type(t_rat2), intent(in ) :: a 
            type(t_rat2), intent(in out), allocatable :: b
            if (a%ideno == 0) print *, '**************!!denominator is 0!!**************'
            if (allocated(b)) then 
                b%inume = a%inume
                b%ideno = a%ideno
            else
                allocate(b, source = a)
            end if    
        end subroutine init_rat2_alloc    

        subroutine init_rat2_alloc_arr(b, a) 
            type(t_rat2), intent(in ) :: a(:)
            type(t_rat2), intent(out), allocatable :: b(:)
            if (any(a%ideno == 0)) print *, '**************!!denominator is 0!!**************'
            allocate(b, source = a)
        end subroutine init_rat2_alloc_arr    



        subroutine fin_rat2(this)
            type(t_rat2), intent(in out) :: this
            print *, '..fin_rat2', this%inume, this%ideno
        end subroutine fin_rat2
    end module m_test


    program mycnstrctr
        use m_test
        implicit none

        print *, 'rational number: 1'
        block 
          type(t_rat1), allocatable :: x, y, z, w(:)

          print *, '  before: x = t_rat1(5, 2)'
          x = t_rat1(5, 2)
          print *, '  before: y = t_rat1(1, 0)'
          y = t_rat1(1, 0)
          print *, '  before: z = y'
          z = y
          print *, '  before y = t_rat1(0, 0)'
          y = t_rat1(0, 0)
          print *, '  before w = t_rat1([1, 1], [2, 2])'
          w = t_rat1([1, 1], [2, 2])
          print *, '  after  w = t_rat1([1, 1], [2, 2])'
          print *, w
          print *, '  before w = [t_rat1(1, 1), t_rat1(2, 2)]'
          w = [t_rat1(1, 0), t_rat1(2, 2)]
          print *, '  after  w = [t_rat1(1, 1), t_rat1(2, 2)]'
          print *, w
          print *, 'end of block'
        end block

        print *
        print *, 'rational number: 2'
        block 
          type(t_rat2), allocatable :: x, y, z, w(:)

          print *, '  before: x = t_rat2(5, 2)'
          x = t_rat2(5, 2)
          print *, '  before: y = t_rat2(1, 0)'
          y = t_rat2(1, 0)
          print *, '  before: z = y'
          z = y
          print *, '  before y = t_rat2(0, 0)'
          y = t_rat2(0, 0)
          print *, '  after  y = t_rat2(0, 0)'
!          w = t_rat2([1, 1], [2, 2])
!          print *, '  after  w = t_rat2([1, 1], [2, 2])'
!          print *, w
          print *, '  before w = [t_rat2(1, 1), t_rat2(2, 2)]'
          w = [t_rat2(1, 0), t_rat2(2, 2)]
          print *, '  after  w = [t_rat2(1, 1), t_rat2(2, 2)]'
          print *, w
          print *, 'end of block'
        end block

        stop 'normal termination'
    end program mycnstrctr

実行結果

intel fortran v.19

実行結果をみると、デフォルト・コンストラクタを上書きする方法では t_rat1 コンストラクタを呼び出すごとに、

 ..fin_rat1           5           2

のような表示がなされ、ファイナライザが起動されていることが分かります。これに対して、assignment 演算子を上書きする方法では、block 構造を抜け出る時にのみ解放処理が行われています。また

          z = y

のような変数間の代入の場合、第1の方法は t_rat1 コンストラクタを明示的に用いていないので、分母 0 チェックがなされません。ところが第2の方法では、代入演算子が上書きされているので、このような場合でも分母 0 チェックがなされています。

配列の扱いにも少し違いが出てきます。第2の方法は、配列用のサブルーチンを用意する必要があり、より厳しい制限がでます。

 rational number: 1
   before: x = t_rat1(5, 2)
 ..fin_rat1           5           2
   before: y = t_rat1(1, 0)
 **************!!denominator is 0!!**************
 ..fin_rat1           1           0
   before: z = y
   before y = t_rat1(0, 0)
 **************!!denominator is 0!!**************
 ..fin_rat1           1           0
 ..fin_rat1           0           0
   before w = t_rat1([1, 1], [2, 2])
 ..fin_rat1           1           2
   after  w = t_rat1([1, 1], [2, 2])
           1           2           1           2
   before w = [t_rat1(1, 1), t_rat1(2, 2)]
 **************!!denominator is 0!!**************
 ..fin_rat1           2           2
 ..fin_rat1           1           0
   after  w = [t_rat1(1, 1), t_rat1(2, 2)]
           1           0           2           2
 end of block
 ..fin_rat1           5           2
 ..fin_rat1           0           0
 ..fin_rat1           1           0

 rational number: 2
   before: x = t_rat2(5, 2)
   before: y = t_rat2(1, 0)
 **************!!denominator is 0!!**************
   before: z = y
 **************!!denominator is 0!!**************
   before y = t_rat2(0, 0)
 **************!!denominator is 0!!**************
   after  y = t_rat2(0, 0)
   before w = [t_rat2(1, 1), t_rat2(2, 2)]
 **************!!denominator is 0!!**************
   after  w = [t_rat2(1, 1), t_rat2(2, 2)]
           1           0           2           2
 end of block
 ..fin_rat2           5           2
 ..fin_rat2           0           0
 ..fin_rat2           1           0
normal termination

第2の方法では左右両辺の型が t_rat2 の場合のみに代入演算子の上書きが有効で、基底クラス t_base を t_rat2 で allocate した場合には、型の組み合わせが一致しないため、普通の代入が行われ分母チェックはなされません。

また、第2の方法では、static に確保された実引数と、allocatable な実引数の区別がつかないので、実行時に区別する必要があります。

まとめ

assignment 演算子の上書きによる方法は、コンストラクタの代わりにはなりえないと思われますが便利な性質もあるので、どうしても重たい一時オブジェクトを作りたくない時などに利用できるかもしれません。

2
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2