Конструктор Фортрана, возвращающий указатель на выделенный объект - PullRequest
2 голосов
/ 03 марта 2020

В этом вопросе: Функции Fortran с указателем приводят к обычному присваиванию , указано, что функции, возвращающие указатели, не рекомендуются.

Мой вопрос касается конструкторов пользовательских типов. Рассмотрим код ниже:

program PointTest

   use PointMod, only: PointType

   implicit none

   class(PointType), allocatable :: TypeObject

   TypeObject = PointType(10)

end program PointTest
module PointMod

   implicit none

   type PointType

      real(8), dimension(:), allocatable :: array

   contains 

      final :: Finalizer

   end type PointType

   interface PointType

      procedure NewPointType

   end interface PointType


contains


   function NewPointType(n) result(TypePointer)

      implicit none

      integer, intent(in) :: n

      type(PointType), pointer :: TypePointer

      allocate(TypePointer)

      allocate(TypePointer%array(n))

   end function NewPointType


   subroutine Finalizer(this)

      implicit none

      type(PointType) :: this

      print *, 'Finalizer called'

   end subroutine Finalizer


end module PointMod

В коде я определил тип с помощью конструктора, который выделяет объект, а затем выделяет массив в объекте. Затем он возвращает указатель на объект.

Если конструктор только что возвратил объект, объект и массив будут скопированы и затем освобождены (по крайней мере, с помощью стандартных совместимых компиляторов). Это может привести к дополнительным расходам и путанице с отслеживанием нашей памяти.

Компиляция вышеуказанного кода с помощью ifort не выдает предупреждений с -warn all (кроме неиспользуемой переменной в финализаторе), и код ведет себя так, как я ожидаю. Он также прекрасно работает с gfortran, за исключением того, что я получаю предупреждение при использовании -Wall

    TypeObject = PointType(10)
                1
Warning: POINTER-valued function appears on right-hand side of assignment at (1) [-Wsurprising]

Каковы риски использования таких конструкторов? Насколько я могу судить, не будет никаких висящих указателей, и у нас будет больше контроля над распределением объектов. Один из обходных путей, который позволил бы достичь того же результата, - это явно выделить объект и превратить конструктор в подпрограмму, которая устанавливает переменные и выполняет выделение массива, но это выглядит намного менее элегантно. Есть ли другие решения? Наш код соответствует стандарту Fortran 2008.

1 Ответ

1 голос
/ 03 марта 2020

Не используйте функции с указателями. Как правило, я никогда не создаю функции, которые возвращают функции. Они плохие и запутанные. Они приводят к неприятным ошибкам, особенно когда кто-то путает => и =.

. Функция выполняет то, что она выделяет новый объект и создает указатель, который выделяет объект.

TypeObject = PointType(10)

делает то, что копирует значение объекта, сохраненного в указателе. Тогда указатель будет забыт, а память, на которую указывал указатель, будет утечка и потеряна навсегда.


Вы пишете «Насколько я могу судить, не будет никаких висящих указателей, и мы будем иметь больше контроля над распределением объектов. " Однако я не вижу способа избежать висящего указателя, выделенного внутри функции. Даже финализатор не может помочь здесь. Я тоже не вижу, как у вас больше контроля. Память, которую вы явно выделяете, просто потеряна. У вас есть другая память для TypeObject (вероятно, в стеке основной программы), и массив внутри типа будет выделен снова во время копирования в присваивании intrinsi c TypeObject = PointType(10).

Финализатор может позаботьтесь о компоненте массива, чтобы не потерять массив, размещенный внутри функции. Однако сам тип, на который указывает указатель TypePointer, с его неразмещаемыми компонентами, дескрипторами и дескрипторами без указателей и т. Д., Не может быть освобожден из финализатора и останется висящим, а память будет вытекать.


Не бойтесь функций, которые возвращают объекты как значения. Это не проблема. Компиляторы умны и способны оптимизировать ненужную копию. Компилятор может легко обнаружить, что вы просто присваиваете результат функции, поэтому он может использовать область памяти цели назначения для переменной результата функции (если она не должна быть размещаемой).

Многие существуют другие оптимизации.

   function NewPointType(n) result(TypePointer)   
      integer, intent(in) :: n

      type(PointType) :: TypePointer

      allocate(TypePointer%array(n))    
   end function NewPointType

проще и должен работать просто отлично. С оптимизацией это может быть даже быстрее. Если использование не указателя неразмещаемого результата невозможно, используйте выделение. Не используйте указатели для результатов функции.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...