Fortran · 심화
객체와 수치 해석으로 깊어지는 Fortran
객체지향 Fortran - 형식 바인딩 프로시저
type 안의 contains, 형식 바인딩 프로시저, extends 상속, class 다형 변수, final 프로시저
개발자KR · 원고 갱신
이 장에서 배우는 것
앞 장에서 모듈의 공개 범위를 정하고 구현을 나누는 방법을 살펴보았다. 이번에는 모듈 안에 둔 파생 타입을 중심으로 데이터와 연산을 묶는다. 기본서에서 만든 파생 타입이 계산에 필요한 값을 담는 그릇이었다면, 이제는 그 값을 어떤 절차로 바꾸고 조회하는지도 타입의 인터페이스에 포함한다.
예제는 2차원 금속판의 온도 배열을 관리하는 작은 프로그램이다. 아직 열 방정식을 풀지는 않는다. 온도 배열을 만들고, 모든 격자점에 같은 온도 증가량을 적용하며, 평균 온도를 확인한다. 이 단순한 작업으로 호출 문법, 상속, 실행 중 프로시저 선택, 객체 정리라는 네 가지 관계를 분리해서 관찰한다.
- 타입 정의 안의
contains에 형식 바인딩 프로시저(type-bound procedure)를 선언한다. - 호출한 객체가 전달 객체 인수(passed-object dummy argument)로 들어가는 방식을 설명한다.
extends로 부모 타입을 확장하고 상속받은 바인딩을 재정의한다.class변수의 선언 타입과 동적 타입을 구분한다.final프로시저가 담당하는 정리 작업과 호출 범위를 이해한다.
문제 상황
금속판 계산 코드를 여러 사람이 함께 수정한다고 가정한다. 한 프로시저는 온도 배열을 할당하고, 다른 프로시저는 온도를 바꾸며, 또 다른 프로시저는 평균을 계산한다. 각 호출에는 금속판 변수를 따로 넘겨야 한다. 배열을 직접 공개하면 호출자가 배열을 해제하거나 크기를 바꾸는 일도 가능하다. 계산을 시작하기 전에 초기화되었는지 확인하는 코드가 여러 곳에 흩어진다.
여기에 온도 증가량의 일부만 반영하는 시험용 금속판을 추가하려 한다. 보통 금속판은 전달받은 증가량을 그대로 적용하고, 시험용 금속판은 그 절반만 적용한다. 호출하는 쪽이 종류마다 다른 프로시저 이름을 기억하면 분기가 늘어난다. 같은 이름으로 요청하되 실제 객체 종류에 맞는 동작을 선택할 수 있으면 호출부가 간결해진다.
이 장의 설계에서는 온도 배열을 비공개 성분으로 두고, 초기화·온도 증가·평균 조회만 공개한다. 보통 금속판과 시험용 금속판은 같은 호출 형태를 제공한다. 두 종류의 차이는 온도 증가를 처리하는 구현에만 둔다. 이 구조는 객체지향 설계의 출발점이며, 모든 계산을 객체로 바꿔야 한다는 뜻은 아니다. 상태와 그 상태를 다루는 연산이 함께 바뀌는 부분부터 묶는 편이 이해하기 쉽다.
타입 안에서 데이터와 연산을 연결한다
파생 타입 정의 안의 contains 앞에는 성분을 선언하고, 뒤에는 바인딩을 선언한다. 여기서 바인딩은 객체를 통해 사용할 이름과 실제 프로시저를 연결하는 선언이다. 프로시저 본문을 타입 정의 안에 쓰는 것은 아니다. 본문은 모듈의 contains 뒤에 둔다. 따라서 한 모듈 안에 나타나는 두 contains는 서로 다른 경계를 만든다.
type :: plate_t
private
real(real64), allocatable :: temperature(:, :)
contains
procedure, public :: warm => plate_warm
end type plate_t
warm은 호출자가 보는 바인딩 이름이고, plate_warm은 그 바인딩이 가리키는 모듈 프로시저 이름이다. 호출은 call plate%warm(4.0_real64)처럼 쓴다. 기본 설정에서는 호출 객체 plate가 실제 프로시저의 첫 번째 인수로 전달된다. 따라서 구현에는 객체 인수와 온도 증가량 인수가 있지만, 호출부에는 온도 증가량만 적는다.
subroutine plate_warm(self, increment)
class(plate_t), intent(inout) :: self
real(real64), intent(in) :: increment
self%temperature = self%temperature + increment
end subroutine plate_warm
self는 언어가 정한 이름이 아니다. 객체를 받는 인수라는 뜻이 드러나도록 선택한 이름이다. 기본적으로 첫 인수가 전달 객체 인수가 되며, pass(인수명)으로 다른 인수를 지정할 수도 있다. nopass를 쓰면 호출 객체가 자동으로 전달되지 않는다. 여기서는 객체 상태를 다루므로 기본 전달 방식을 사용한다.
확장 가능한 타입의 전달 객체 인수는 해당 타입의 class로 선언한다. 그러면 부모 타입의 구현을 자식 객체에도 적용할 수 있다. intent(inout)은 객체의 온도 배열을 변경한다는 계약이다. 평균을 읽기만 하는 함수에는 intent(in)을 사용한다.
비공개 성분은 호출자가 내부 표현에 직접 의존하지 않게 한다. 완성 코드에서는 temperature를 밖에서 읽거나 바꿀 수 없다. 대신 initialize, warm, mean_temperature를 공개한다. 타입 안의 private로 기본 접근성을 제한하고, 공개할 바인딩에 public을 적는다. 모듈 전체의 기본 접근성도 비공개로 두고 두 타입 이름만 공개한다.
| 위치 또는 이름 | 역할 | 예제에서의 내용 |
|---|---|---|
| 타입 안의 contains 앞 | 객체의 상태 선언 | temperature 배열 |
| 타입 안의 contains 뒤 | 바인딩과 종료 절차 선언 | warm, final |
| 모듈의 contains 뒤 | 프로시저 구현 | plate_warm의 본문 |
| warm | 호출자가 사용하는 이름 | plate%warm(...) |
상속과 class로 같은 호출을 다른 동작에 연결한다
상속(inheritance)은 부모 타입의 성분과 바인딩을 이어받아 새로운 타입을 정의하는 방법이다. type, extends(plate_t) :: scaled_plate_t라고 선언하면 새 타입에 부모 부분이 포함된다. 완성 코드의 자식 타입은 온도 증가량에 곱할 gain 성분을 추가한다. 초기화와 평균 조회는 부모의 바인딩을 그대로 사용한다.
자식 타입의 contains에서 부모와 같은 바인딩 이름을 선언하면 재정의(overriding)가 이루어진다. 예제에서는 warm을 scaled_plate_warm에 연결한다. 부모와 자식의 구현 이름이 달라도 바인딩 이름은 같다. 재정의에서는 전달 객체 인수의 타입이 자식 타입으로 바뀌지만, 나머지 인수의 이름과 특성 등은 부모 바인딩의 인터페이스와 맞아야 한다.
type, extends(plate_t) :: scaled_plate_t
private
real(real64) :: gain = 0.5_real64
contains
procedure, public :: warm => scaled_plate_warm
end type scaled_plate_t
type(plate_t) 변수는 그 타입의 객체를 나타낸다. 반면 class(plate_t)는 부모 타입과 그 확장 타입의 객체를 받을 수 있는 다형성(polymorphism)을 표현한다. 이 장에서는 할당 가능한 다형 변수를 사용한다. class(plate_t), allocatable :: plate에서 선언 타입은 plate_t다. 이후 allocate(scaled_plate_t :: plate)를 실행하면 할당된 객체의 동적 타입(dynamic type)은 scaled_plate_t가 된다.
동적 타입은 실행 중 실제로 만들어진 객체의 타입이다. plate%warm(...)은 이 동적 타입에 따라 자식의 구현을 선택한다. 이를 동적 디스패치(dynamic dispatch)라고 한다. 호출부가 부모 타입의 이름으로 작성되어 있어도 재정의된 동작이 실행된다.
다형 변수라고 해서 자식 전용 성분을 모두 바로 사용할 수 있는 것은 아니다. 선언 타입이 제공하는 인터페이스가 기본 접근 범위다. 이 장에서는 gain을 자식의 내부 설정으로 두고, 호출자가 부모와 공통인 바인딩만 사용하게 한다. 여러 종류의 객체를 더 넓게 선택하고 구분하는 설계는 다음 장에서 이어간다.
자식 구현에서 부모의 동작을 재사용할 수도 있다. 자식 객체의 부모 부분은 부모 타입 이름으로 접근한다. 예제의 self%plate_t%warm(increment * self%gain)은 부모 부분에 온도 증가를 맡긴다. 부모 부분은 부모 타입의 객체이므로 이 호출은 부모 구현으로 연결된다. 자식의 warm을 다시 호출하는 것이 아니다.
이 방식은 배열 갱신과 초기화 검사라는 공통 동작을 부모에 남긴다. 자식 구현은 증가량을 조정하는 일만 담당한다. 상속의 가치는 같은 코드를 줄이는 데서 끝나지 않는다. 호출부가 공통 인터페이스에 의존하도록 만들어 객체 종류의 차이를 구현 쪽으로 옮긴다는 데 있다.
final로 객체의 정리 지점을 표현한다
종료 프로시저(final subroutine)는 객체가 정리되는 특정 상황에 호출되는 서브루틴이다. 타입 안에서 final :: plate_finalize처럼 선언한다. 일반 바인딩과 달리 객체에 %를 붙여 호출하는 인터페이스가 아니다. 종료 인수는 해당 타입의 비다형 인수로 선언해야 하므로, 예제에서는 type(plate_t), intent(inout) :: self를 사용한다.
이번 프로그램의 종료 프로시저는 온도 배열이 할당되어 있으면 해제한다. 다만 할당 가능한 성분은 객체가 정리될 때 언어가 해제하므로, 이 배열만 관리한다면 사용자 정의 종료 프로시저가 없어도 된다. 여기서는 종료 절차를 선언하고 실행 시점을 이해하기 위해 명시적으로 해제한다. 이후 파일 연결처럼 별도의 정리 동작이 필요한 상태를 추가한다면 그 정리 정책을 같은 위치에 둘 수 있다.
할당된 다형 객체를 deallocate하면 실제 동적 타입에 맞는 종료 과정이 진행된다. 자식 객체를 정리할 때는 부모 부분의 종료 과정도 포함된다. 예제의 자식 타입에는 별도 종료 프로시저가 없지만, 부모 부분의 plate_finalize가 온도 배열을 정리한다.
범위를 벗어나는 일반 지역 객체의 종료와 프로그램 자체의 종료를 같은 것으로 생각하면 안 된다. 특히 주 프로그램의 끝에서 모든 객체의 종료 프로시저가 호출된다고 기대해서는 안 된다. 완성 코드에서는 지역 객체를 block 안에 두고, 다형 객체는 그 안에서 명시적으로 해제한다. 블록을 벗어날 때 보통 금속판 객체도 정리된다. 종료 프로시저 안에는 출력문을 넣지 않아 계산 결과와 정리 관찰을 섞지 않는다.
종료 프로시저의 인수 차원도 중요하다. 이 장의 종료 프로시저는 스칼라 객체를 대상으로 한다. 객체 배열 전체의 종료를 설계할 때는 해당 차원에 맞는 종료 프로시저나 원소별로 적용 가능한 종료 프로시저를 검토해야 한다. 온도 성분이 2차원 배열이라는 사실과 금속판 객체 자체가 배열이라는 사실은 서로 다르다.
완성 코드
다음 코드를 main.f90로 저장한다. 모듈을 먼저 정의하고 그 모듈을 사용하는 주 프로그램을 뒤에 둔다. 모든 격자점의 초기 온도는 20.0이다. 보통 금속판에는 4.0을 한 번 더하고, 시험용 금속판에는 같은 요청을 두 번 보낸다. 시험용 금속판은 요청의 절반씩 반영하므로 평균이 22.0, 24.0으로 바뀐다.
main.f90
module plate_objects
use iso_fortran_env, only : real64
implicit none
private
public :: plate_t, scaled_plate_t
type :: plate_t
private
real(real64), allocatable :: temperature(:, :)
contains
procedure, public :: initialize => plate_initialize
procedure, public :: warm => plate_warm
procedure, public :: mean_temperature => plate_mean
final :: plate_finalize
end type plate_t
type, extends(plate_t) :: scaled_plate_t
private
real(real64) :: gain = 0.5_real64
contains
procedure, public :: warm => scaled_plate_warm
end type scaled_plate_t
contains
subroutine plate_initialize(self, nx, ny, initial_temperature)
class(plate_t), intent(inout) :: self
integer, intent(in) :: nx, ny
real(real64), intent(in) :: initial_temperature
if (nx < 1 .or. ny < 1) error stop 1
if (allocated(self%temperature)) deallocate(self%temperature)
allocate(self%temperature(nx, ny))
self%temperature = initial_temperature
end subroutine plate_initialize
subroutine plate_warm(self, increment)
class(plate_t), intent(inout) :: self
real(real64), intent(in) :: increment
if (.not. allocated(self%temperature)) error stop 2
self%temperature = self%temperature + increment
end subroutine plate_warm
function plate_mean(self) result(value)
class(plate_t), intent(in) :: self
real(real64) :: value
if (.not. allocated(self%temperature)) error stop 3
value = sum(self%temperature) / real(size(self%temperature), real64)
end function plate_mean
subroutine scaled_plate_warm(self, increment)
class(scaled_plate_t), intent(inout) :: self
real(real64), intent(in) :: increment
call self%plate_t%warm(increment * self%gain)
end subroutine scaled_plate_warm
subroutine plate_finalize(self)
type(plate_t), intent(inout) :: self
if (allocated(self%temperature)) deallocate(self%temperature)
end subroutine plate_finalize
end module plate_objects
program main
use iso_fortran_env, only : real64
use plate_objects, only : plate_t, scaled_plate_t
implicit none
block
type(plate_t) :: ordinary
class(plate_t), allocatable :: plate
call ordinary%initialize(3, 2, 20.0_real64)
call ordinary%warm(4.0_real64)
write (*, '(A,F8.2)') 'ordinary mean: ', ordinary%mean_temperature()
allocate(scaled_plate_t :: plate)
call plate%initialize(3, 2, 20.0_real64)
call plate%warm(4.0_real64)
write (*, '(A,F8.2)') 'scaled mean 1: ', plate%mean_temperature()
call plate%warm(4.0_real64)
write (*, '(A,F8.2)') 'scaled mean 2: ', plate%mean_temperature()
deallocate(plate)
end block
end program main
줄별 해설
use iso_fortran_env, only : real64는 온도 계산에 사용할 실수 종류를 가져온다. 상수에도 _real64를 붙여 같은 종류로 계산한다. 모듈과 주 프로그램 모두에서 이 이름을 사용하므로 각각 가져온다. 모듈에서 가져온 이름을 비공개로 두었기 때문에 주 프로그램이 타입을 가져오는 것만으로 real64까지 사용할 수 있는 것은 아니다.
모듈의 private와 public :: plate_t, scaled_plate_t는 외부 인터페이스를 두 타입으로 제한한다. plate_warm 같은 구현 이름은 외부에 공개하지 않는다. 호출자는 구현 이름 대신 공개 바인딩인 warm을 사용한다. 구현 프로시저가 비공개여도 공개 바인딩을 통한 호출은 가능하다.
temperature(:, :)는 객체마다 따로 보유하는 할당 가능한 2차원 배열이다. plate_initialize는 크기가 양수인지 먼저 확인하고, 이미 할당된 배열이 있으면 해제한 뒤 새 크기로 할당한다. 따라서 같은 객체를 다시 초기화할 수 있다. 이 초기화는 종료 절차와 구분된다. 배열 성분 하나를 해제하는 것이 금속판 객체 자체를 종료하는 것은 아니다.
plate_warm은 배열이 준비되었는지 검사하고 배열 전체에 같은 증가량을 더한다. 검사에 실패하면 계산을 계속하지 않고 숫자 종료 코드로 중단한다. 정상 실행에서는 이 경로에 들어가지 않는다. 초기화되지 않은 상태에서 평균을 구하는 경우도 plate_mean이 검사한다.
sum은 모든 온도의 합을 구하고, size는 전체 원소 수를 구한다. 원소 수를 real(..., real64)로 바꿔 실수 나눗셈으로 평균을 계산한다. 초기화가 양의 크기만 허용하므로 정상 객체의 원소 수는 0이 아니다. 격자 크기를 따로 성분에 저장하지 않아 배열 크기와 별도 크기 정보가 어긋날 가능성도 줄인다.
scaled_plate_t는 부모의 배열과 세 바인딩을 이어받는다. 이 중 warm만 새 구현으로 바꾼다. gain은 기본값 0.5를 가진다. 부모의 초기화는 부모 부분의 온도 배열을 다루며 이 자식 성분을 바꾸지 않는다. 따라서 상속받은 초기화를 호출한 뒤에도 배율은 0.5다.
scaled_plate_warm의 첫 인수는 class(scaled_plate_t)다. 부모 바인딩의 increment와 같은 이름과 선언을 사용해 인터페이스를 맞춘다. 그 다음 부모 부분에 배율을 적용한 증가량을 전달한다. 초기화 검사와 실제 배열 변경은 부모 구현에서 수행한다.
plate_finalize는 전달 객체 인수를 쓰는 일반 바인딩이 아니다. 종료 대상 객체를 받는 인수는 type(plate_t)다. 이미 배열이 해제되어 있을 수도 있으므로 allocated를 확인한다. 종료 프로시저에서 성분을 해제한 뒤에는 자동 성분 정리 단계에서 그 성분을 다시 해제할 필요가 없다.
주 프로그램의 ordinary는 보통 금속판 객체다. plate는 선언 타입이 부모인 할당 가능한 다형 변수다. 자식 타입을 명시한 allocate가 실제 객체 종류를 결정한다. 이후 초기화와 평균 조회는 상속된 구현을 사용하고, 온도 증가는 재정의된 구현을 사용한다.
| 호출 | 실제 객체 종류 | 선택되는 구현 |
|---|---|---|
| ordinary%warm | plate_t | plate_warm |
| plate%initialize | scaled_plate_t | plate_initialize |
| plate%warm | scaled_plate_t | scaled_plate_warm |
| plate%mean_temperature | scaled_plate_t | plate_mean |
마지막의 deallocate(plate)는 할당된 자식 객체를 정리한다. 이어서 end block에서 지역 객체 ordinary의 종료가 이루어진다. 두 객체의 온도 배열은 서로 독립적이다. 한쪽을 정리해도 다른 쪽 온도가 바뀌지 않는다.
실행 결과
macOS와 Linux에서 GNU Fortran 16으로 다음과 같이 컴파일하고 실행한다. 자유 형식 소스이며 별도 입력 파일이나 표준 입력은 필요하지 않다.
gfortran -std=f2018 -Wall main.f90 -o plate_demo
./plate_demo
정상 실행의 예상 출력은 다음과 같다. F8.2는 실수 출력에 여덟 칸을 사용하고 소수점 아래 두 자리를 표시한다. 문자열 끝의 공백에 서식의 앞쪽 공백이 더해져 숫자 앞에는 네 칸이 생긴다.
ordinary mean: 24.00
scaled mean 1: 22.00
scaled mean 2: 24.00
여섯 격자점이 모두 같은 값으로 시작하고 같은 증가량을 받으므로 평균은 각 격자점의 온도와 같다. 시험용 금속판의 첫 결과가 22.00인 것은 부모 인터페이스로 호출해도 자식 구현이 선택되었음을 보여 준다. 두 번째 결과가 24.00인 것은 첫 호출의 상태 변화가 객체에 남아 있음을 보여 준다.
실무에서 자주 틀리는 것
전달 객체 인수를 type으로 선언한다
확장 가능한 타입의 일반 바인딩에서 전달 객체 인수를 비다형으로 선언하면 바인딩 요구 사항에 맞지 않는다. 부모 구현이 자식 객체도 받을 수 있도록 class를 사용한다. 다음은 잘못된 인수 선언이다.
subroutine plate_warm(self, increment)
type(plate_t), intent(inout) :: self
real(real64), intent(in) :: increment
end subroutine plate_warm
고친 선언은 다음과 같다. 종료 프로시저의 인수 규칙과 혼동하지 않아야 한다.
subroutine plate_warm(self, increment)
class(plate_t), intent(inout) :: self
real(real64), intent(in) :: increment
end subroutine plate_warm
고정 타입 변수에 자식 객체를 할당하려 한다
allocatable은 할당 여부를 바꿀 수 있게 하지만, 그 자체로 객체 종류를 바꿀 수 있게 하지는 않는다. 다음 변수는 비다형이므로 자식 타입을 지정한 할당과 맞지 않는다.
type(plate_t), allocatable :: plate
allocate(scaled_plate_t :: plate)
부모와 자식 중 실제 객체 종류를 선택하려면 다형 변수로 선언한다.
class(plate_t), allocatable :: plate
allocate(scaled_plate_t :: plate)
할당된 상태에서 다른 종류로 다시 할당하려면 기존 객체를 먼저 해제해야 한다. 해제 이후에는 객체가 없으므로 바인딩을 호출하기 전에 다시 할당하고 초기화해야 한다.
자식 구현에서 자기 바인딩을 다시 호출한다
부모 동작을 재사용하려다가 아래처럼 쓰면 실제 동적 타입에 따라 자식의 warm으로 다시 연결된다. 의도한 부모 호출 대신 재귀 호출이 된다.
call self%warm(increment * self%gain)
부모 부분을 명시하면 부모 바인딩을 호출한다.
call self%plate_t%warm(increment * self%gain)
이 차이는 호출 대상의 이름만으로 판단하기 어렵다. 호출 대상이 전체 자식 객체인지, 그 안의 부모 부분인지 함께 보아야 한다.
final을 일반 바인딩처럼 선언하고 호출한다
종료 절차를 공개 바인딩처럼 적거나 객체에 붙여 직접 호출하려는 코드는 종료 프로시저의 선언 방식과 맞지 않는다.
! 잘못된 종료 선언과 호출
final, public :: plate_finalize
call plate%plate_finalize()
종료 선언에는 바인딩 접근성 속성을 붙이지 않는다. 할당된 객체의 수명을 끝내려면 객체를 해제한다.
! 타입 정의 안
final :: plate_finalize
! 할당된 객체를 사용하는 범위 안
deallocate(plate)
객체를 유지한 채 상태만 비우고 싶다면 별도의 공개 바인딩을 설계한다. 그 경우 초기화 전 상태에서 어떤 연산을 허용할지 함께 정해야 한다. 객체 종료와 상태 초기화는 서로 다른 요청이다.
한눈에 보기
| 문법 | 뜻 | 이 장의 적용 | 주의점 |
|---|---|---|---|
| 타입 안의 contains | 성분과 바인딩의 경계 | 온도 배열 뒤에 배치 | 구현 본문은 모듈에 둔다 |
| procedure :: 이름 => 구현 | 호출 이름을 구현에 연결 | warm 연결 | 기본적으로 첫 인수에 객체가 전달된다 |
| extends(부모) | 부모 부분을 포함하는 확장 | 배율 성분 추가 | 재정의 인터페이스를 맞춘다 |
| class(부모) | 부모와 확장 타입 수용 | 자식 객체를 plate에 할당 | 선언 타입과 동적 타입을 구분한다 |
| self%부모%바인딩 | 부모 부분의 동작 호출 | 조정한 증가량 전달 | self%바인딩과 호출 대상이 다르다 |
| final :: 프로시저 | 종료 절차 등록 | 온도 배열 정리 | 종료 인수는 해당 타입의 type 선언이다 |
이번 구조에서 호출자는 초기화, 온도 증가, 평균 조회라는 요청만 알고 있다. 배열 표현은 부모 타입 안에 있고, 증가량의 조정 규칙은 자식 타입 안에 있다. 객체의 종류가 달라도 공통 요청을 유지할 수 있다는 점이 다음 설계의 기반이 된다.
연습 문제
- 시험용 금속판의
gain기본값을 0.25로 바꾼다. 주 프로그램의 나머지 부분을 유지했을 때 세 출력의 평균 온도를 구하고, 수정할 선언을 적는다. - 평균 대신 최대 온도를 조회하는 공개 바인딩
maximum_temperature를 부모 타입에 추가한다. 자식 타입에 별도 바인딩을 선언해야 하는지 설명한다. allocate(scaled_plate_t :: plate)를allocate(plate_t :: plate)로 바꾼다. 두 번의plate%warm호출이 어느 구현으로 연결되며 어떤 평균을 출력하는지 설명한다.- 초기화 직후, 온도 증가 호출 전에
deallocate(plate)를 실행하도록 옮겼다. 왜 이후 호출이 유효하지 않은지 설명하고, 온도를 두 번 갱신하는 원래 목적에 맞게 해제 위치를 고친다.
정답과 해설
-
부모 객체의 평균은 그대로 24.00이다. 자식 객체는 한 번에 1.0씩 증가하므로 첫 평균은 21.00, 두 번째 평균은 22.00이다. 자식 타입의 성분 선언만 다음처럼 바꾼다.
real(real64) :: gain = 0.25_real64이 수정은 객체 생성 시 적용되는 기본값을 바꾼다. 부모의 초기화 프로시저는 이 성분을 변경하지 않는다.
-
부모 타입의
contains뒤에 공개 바인딩을 추가하고, 모듈의contains뒤에 구현을 추가한다.procedure, public :: maximum_temperature => plate_maximumfunction plate_maximum(self) result(value) class(plate_t), intent(in) :: self real(real64) :: value if (.not. allocated(self%temperature)) error stop 4 value = maxval(self%temperature) end function plate_maximum자식 타입은 이 바인딩을 상속하므로 별도 선언이 필요하지 않다. 현재 예제는 모든 격자점의 온도가 같으므로 최대 온도와 평균 온도가 같다. 이후 격자마다 온도가 달라지면 두 조회의 의미가 구분된다.
-
plate의 선언 타입은 계속plate_t이고, 할당된 객체의 동적 타입도plate_t가 된다. 두 호출은 모두plate_warm으로 연결된다. 초기 온도 20.0에 4.0씩 더하므로 해당 두 줄은 24.00과 28.00을 출력한다. 출력 문자열에 남아 있는scaled도 실제 객체 종류에 맞게 바꾸는 편이 좋다.class선언은 여러 타입을 받을 가능성을 열어 둔다. 그 선언만으로 자식 객체가 만들어지거나 자식 구현이 선택되는 것은 아니다. -
해제하면 할당된 금속판 객체와 그 상태가 정리된다. 이후
plate%warm은 할당되지 않은 다형 변수를 대상으로 하므로 유효한 호출이 아니다. 평균 조회도 할 수 없다. 원래 목적에서는 두 온도 증가와 두 출력이 끝난 뒤에 해제해야 한다.call plate%warm(4.0_real64) write (*, '(A,F8.2)') 'scaled mean 1: ', plate%mean_temperature() call plate%warm(4.0_real64) write (*, '(A,F8.2)') 'scaled mean 2: ', plate%mean_temperature() deallocate(plate)객체를 다시 사용해야 한다면 다시 할당하고 초기화한다. 종료 프로시저는 객체의 수명을 정리하며, 이전 온도를 보존해서 다음 객체에 넘기는 역할은 하지 않는다.
READER FEEDBACK
질문·의견
내용에 관한 질문이나 더 나은 설명을 위한 의견을 남겨 주세요. 오탈자는 위의 제보 양식이 더 빨리 반영됩니다. 이 댓글은 원래 게시글과 같은 자리에 쌓입니다.
댓글 0
아직 댓글이 없습니다. 첫 댓글을 남겨 보세요.