절차적 타입
절차적 타입 (Procedural types)
함수나 프로시저도 변수에 담아 넘겨주고 싶을 때가 있어요. 그걸 가능하게 해 주는 게 절차적 타입(procedural type) 이에요. Free Pascal에서는 Turbo Pascal이나 Delphi와 조금 다르게 동작하니, 그 차이를 중심으로 살펴볼게요.
출처: 문서
본문
Free Pascal은 절차적 타입을 지원해요. 다만 Turbo Pascal이나 Delphi의 구현과는 조금 달라요. 타입 선언 자체는 다음 문법 다이어그램처럼 동일해요.
Procedural types
-> Procedure [ formal parameter list ] [ calling convention ]
-> Function [ formal parameter list ] : result type [ calling convention ]
[ of object ] [ is nested ]
형식 매개변수 목록의 설명은 매뉴얼 14장(740쪽)을 참고하세요. 다음 두 가지는 유효한 타입 선언이에요:
Type TOneArg = Procedure (Var X : integer);
TNoArg = Function : Real;
var proc : TOneArg;
func : TNoArg;
절차적 타입의 변수에는 다음 값들을 할당할 수 있어요:
Nil— 일반 프로시저 포인터와 메서드 포인터 모두 가능해요.- 절차적 타입의 변수 참조 — 즉 같은 타입의 다른 변수.
- 전역 프로시저·함수의 주소 — 함수/프로시저 헤더와 호출 규약이 일치해야 해요.
- 메서드(method) 주소.
이런 선언이 있을 때 다음 할당들은 유효해요:
Procedure printit (Var X : Integer);
begin
WriteLn (x);
end;
...
Proc := @printit;
Func := @Pi;
이 예시에서 Turbo Pascal과의 차이가 분명히 드러나요. Turbo Pascal에서는 절차적 타입 변수에 값을 할당할 때 주소 연산자(@)를 쓸 필요가 없었는데, Free Pascal에서는 필수예요. 다만 -MDelphi나 -MTP 스위치를 쓰면 주소 연산자를 생략할 수 있어요.
주의: 호출 규약에 관한 수식어는 선언과 일치해야 해요. 즉 다음 코드는 오류가 됩니다:
Type TOneArgCcall = Procedure (Var X : integer);cdecl;
var proc : TOneArgCcall;
Procedure printit (Var X : Integer);
begin
WriteLn (x);
end;
begin
Proc := @printit;
end.
TOneArgCcall 타입이 cdecl 호출 규약을 쓰는 프로시저이기 때문이에요.
만약 is nested 수식어를 추가하면, 그 절차적 변수는 중첩 프로시저와 함께 쓸 수 있어요. 그러려면 소스를 macpas 모드나 ISO 모드로 컴파일하거나, nestedprocvars 모드 스위치를 켜야 해요:
{$modeswitch nestedprocvars}
program tmaclocalprocparam3;
type
tnestedprocvar = procedure is nested;
var
tempp: tnestedprocvar;
procedure p1( pp: tnestedprocvar);
begin
tempp:=pp;
tempp
end;
procedure p2( pp: tnestedprocvar);
var
localpp: tnestedprocvar;
begin
localpp:=pp;
p1( localpp)
end;
procedure n;
begin
writeln( 'calling through n')
end;
procedure q;
var qi: longint;
procedure r;
begin
if qi = 1 then
writeln( 'success for r')
else
begin
writeln( 'fail');
halt( 1)
end
end;
begin
qi:= 1;
p1( @r);
p2( @r);
p1( @n);
p2( @n);
end;
begin
q;
end.
클래스의 메서드를 절차적 타입 변수에 할당하려면, 절차적 타입을 of object 수식어로 선언해야 해요.
다음 두 가지는 메서드 절차적 변수(GUI 설계에서 흔해 이벤트 핸들러(event handler) 라고도 불러요)를 위한 유효한 타입 선언이에요:
Type TOneArg = Procedure (Var X : integer) of object;
TNoArg = Function : Real of object;
var
oproc : TOneArg;
ofunc : TNoArg;
시그니처가 맞는 메서드는 이 함수들에 할당할 수 있어요. 호출할 때 Self는 메서드를 할당할 때 사용한 객체의 인스턴스를 가리켜요.
oproc과 ofunc에 할당할 수 있는 객체 메서드는 다음과 같아요:
Type
TMyObject = Class(TObject)
Procedure DoX (Var X : integer);
Function DoY: Real;
end;
Var
M : TMyObject;
begin
oproc:[email protected];
ofunc:[email protected];
end;
oproc과 ofunc를 호출하면 Self는 M과 같아요.
이 메커니즘을 위임(Delegation) 이라고 부르기도 해요.
주의: method 타입의 두 변수를 비교할 때는 메서드의 주소만 비교하고, 인스턴스 포인터는 비교하지 않아요. 즉 다음 프로그램은 True를 출력해요:
Type
TSomeMethod = Procedure of object;
TMyObject = Class(TObject)
Procedure DoSomething;
end;
Procedure TMyObject.DoSomething;
begin
Writeln('In DoSomething');
end;
var
X,Y : TMyObject;
P1,P2 : TSomeMethod;
begin
X:=TMyObject.Create;
Y:=TMyObject.Create;
P1:[email protected];
P2:[email protected];
Writeln('Same method : ',P1=P2);
end.
두 포인터를 모두 비교해야 한다면 TMethod로 형변환(typecast)한 뒤 비교해야 해요. TMethod는 system 유닛에서 다음과 같이 정의돼 있어요:
TMethod = record
Code : CodePointer;
Data : Pointer;
end;
따라서 다음 프로그램은 False를 출력해요:
Type
TSomeMethod = Procedure of object;
TMyObject = Class(TObject)
Procedure DoSomething;
end;
Procedure TMyObject.DoSomething;
begin
Writeln('In DoSomething');
end;
var
X,Y : TMyObject;
P1,P2 : TMethod;
begin
X:=TMyObject.Create;
Y:=TMyObject.Create;
P1:=TMethod(@X.DoSomething);
P2:=TMethod(@Y.DoSomething);
Writeln('Same method : ',(P1.Data=P2.Data) and (P1.Code=P1.Code));
end.
더 알아보기
- 메서드 포인터와 이벤트 핸들러는 객체 지향 프로그래밍 절에서 더 자세히 다뤄요.
- 메서드 주소의 실제 구조는 system 유닛의
TMethod,CodePointer정의를 참고하세요.