절차적 타입

절차적 타입 (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는 메서드를 할당할 때 사용한 객체의 인스턴스를 가리켜요.

oprocofunc에 할당할 수 있는 객체 메서드는 다음과 같아요:

Type

TMyObject = Class(TObject)

Procedure DoX (Var X : integer);

Function DoY: Real;

end;

Var

M : TMyObject;

begin

oproc:[email protected];

ofunc:[email protected];

end;

oprocofunc를 호출하면 SelfM과 같아요.

이 메커니즘을 위임(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 정의를 참고하세요.