Intel® Fortran Compiler
Build applications that can scale for the future with optimized code designed for Intel® Xeon® and compatible processors.
29361 Discussions

Allocatable array in the derived type

Eugene_S_1
Beginner
2,138 Views

Hello,

Suppose I have the following class:

interface my_type
    module procedure :: ctor
end interface

type :: my_type
    byte, allocatable :: buff(:)
contains
    final :: dtor
    procedure :: putData
    ...
end type

subroutine dtor(this)
    type(my_type) :: this
    if (allocated(this.buff)) deallocate(this.buff)
end subroutine

function ctor() result(this)
    type(my_type) :: this
    allocate(this.buff(100))
    ! Fill buff with some initial data ...
end function

The subroutine putData puts data into buff (its size will grow if need be, I use move_alloc), but at the very beginning some initial data should be put into it. The main code: 

type(my_type) :: a
a = my_type()
call a.putData(...)

It seems that using constructor in this way is not a good idea, because on assignment a = my_type() a copy of the allocated buffer is created and the original buffer (created in ctor) is destroyed. I see two immediate solutions: 1) introduce the special subroutine for initialization, 2) perform initialization on the first call to putData (using e.g. the logical flag isInitialized). Both seem somewhat deficient: the first one introduces additional subroutine that one should not forget to call after ctor, and the second one complicates the code for I have many putData's for different data types (it is a generic name).

Is it possible to devise something better? Thanks.

0 Kudos
1 Solution
IanH
Honored Contributor III
2,138 Views

Note your finalizer does nothing useful!  Allocatable subobjects of an entity being finalized are going to be deallocated automatically anyway.

I don't think Fortran 2003 compiler optimisers are at this point yet, but note - because your type has a finalizer, the language requires the compiler to create a temporary for the function result of your ctor procedure, then requires that result to be assigned as per the assignment statement that reference the constructor, then requires the finalizer to be called on the function result (the thing being assigned to will also be finalized at some point in that sequence).  However, if you didn't have a finalizer (or perhaps if all finalizers were pure), then a very smart compiler would know that there was no way you could tell, in a conforming program, that a temporary ever existed, and hence it could simply construct the function result directly in the variable on the left hand side of the assignment.

If the likely potential of a temporary bothers you, then I would just write a subroutine to do the construction.  The function form of construction is handy if you are going to use the object in an expression (which generally implies more temporaries), but if all you are going to do is immediately assign the constructed object, then all the function form offers is a possibly less performant syntactic nicety without the flexibility of returning "I failed to construct" flags or similar.

Note that your derived type already has a "I am not initialized flag" - you can use the allocation status of the component.

(The component declaration with the `byte` type specifier and the use of a period as a part separator are not conforming Fortran.  I very strongly recommend against using those.)

 

View solution in original post

0 Kudos
5 Replies
Arjen_Markus
Honored Contributor II
2,138 Views

Since putData may require the buffer to be resized, why would it complicate matters so much to introduce the logic to do the initialisation there?

You could for instance do:

subroutine putData( a, data )
   call a%ensureCapacity( the_calculated_minimum_size )
   ...
end subroutine putData

That way you put the requirement for making sure that the buffer exists and is large enough in a separate routine. The only extra bit is calling it in every version of putData, but it is only one line and you will that logic anyway.

 

0 Kudos
Eugene_S_1
Beginner
2,138 Views

The problem is not in the buffer resizing, but with its initialization: prior to any putData call, some initial data (header) should be placed into it. I want this to be done in the constructor, but at same time I want to avoid buffer copying during the "a = my_type()" assignment.

0 Kudos
Arjen_Markus
Honored Contributor II
2,138 Views

You might take advantage of the defined assignment feature - overload the assignment by a routine of your own. That way you can put the new buffer directly in the left-hand side via move_alloc. Something along these lines:

subroutine assign_my_type( left, right)
     type(my_type), intent(inout) :: left
     type(my_type), intent(in)     :: right
     if ( .not. associated( left%buff) ) then
         call move_alloc( right%buff, left%buff )
    endif
    ... other stuff ...
end subroutine assign_my_type

You may introduce a flag to make sure that the right-hand side is coming from the constructor function.

0 Kudos
IanH
Honored Contributor III
2,139 Views

Note your finalizer does nothing useful!  Allocatable subobjects of an entity being finalized are going to be deallocated automatically anyway.

I don't think Fortran 2003 compiler optimisers are at this point yet, but note - because your type has a finalizer, the language requires the compiler to create a temporary for the function result of your ctor procedure, then requires that result to be assigned as per the assignment statement that reference the constructor, then requires the finalizer to be called on the function result (the thing being assigned to will also be finalized at some point in that sequence).  However, if you didn't have a finalizer (or perhaps if all finalizers were pure), then a very smart compiler would know that there was no way you could tell, in a conforming program, that a temporary ever existed, and hence it could simply construct the function result directly in the variable on the left hand side of the assignment.

If the likely potential of a temporary bothers you, then I would just write a subroutine to do the construction.  The function form of construction is handy if you are going to use the object in an expression (which generally implies more temporaries), but if all you are going to do is immediately assign the constructed object, then all the function form offers is a possibly less performant syntactic nicety without the flexibility of returning "I failed to construct" flags or similar.

Note that your derived type already has a "I am not initialized flag" - you can use the allocation status of the component.

(The component declaration with the `byte` type specifier and the use of a period as a part separator are not conforming Fortran.  I very strongly recommend against using those.)

 

0 Kudos
Eugene_S_1
Beginner
2,138 Views

Now everything seems to be clear. Thank you very much for the explanation.

0 Kudos
Reply