제네릭 타입 정의(Generic Type Definition)
제네릭 타입 정의(Generic Type Definition)
제네릭을 처음 만나는 순간의 핵심은 타입 자리표시자(placeholder)예요. 보통 type 정의와 거의 비슷한데, 타입이 들어갈 자리에 아직 정해지지 않은 이름을 써 두는 차이가 있어요. 그 이름은 클래스가 특수화될 때 실제 타입으로 채워져요.
본문
제네릭 타입 정의는 일반 타입 정의와 아주 비슷해요. 차이는 타입을 위한 자리표시자 목록을 포함한다는 점이에요. 아래 구문 다이어그램에서 확인할 수 있어요.
Generic class types
클래스, 객체, 프로시저 타입, 확장 레코드의 경우, 제네릭 타입 선언 뒤에는 **타입 구현(type implementation)**이 따라와야 해요. 일반 클래스 구현과 같지만, 한 가지 예외가 있어요. 바로 템플릿 식별자와 이름이 같은 식별자는 반드시 타입 식별자여야 한다는 점이에요.
그래서 제네릭 타입 선언은 일반 타입 선언과 거의 같아요. 다만 아직 알려지지 않은 타입이 존재한다는 점만 달라요. 그 알 수 없는 타입들은 자리표시자 목록에 나열되고, 클래스가 특수화되기 전까지는 알 수 없어요.
다음은 유효한 제네릭 클래스 정의예요.
Type
generic TList<_T>=class(TObject)
Public
type
TCompareFunc = function(const Item1, Item2: _T): Integer;
var
data : _T;
procedure Add(item: _T);
procedure Sort(compare: TCompareFunc);
end;
이 클래스는 다음과 같이 구현을 뒤에 붙일 수 있어요.
procedure TList.Add(item: _T);
begin
data:=item;
end;
procedure TList.Sort(compare: TCompareFunc);
begin
if compare(data, 20) <= 0 then
halt(1);
end;
이 선언과 구현에서 몇 가지 주목할 점이 있어요.
- 자리표시자는 하나인
_T뿐이에요. 제네릭 클래스가 특수화될 때 타입 식별자로 대체돼요. _T식별자는 타입 자리표시자 외의 다른 용도로 쓸 수 없어요. 즉 아래 코드는 유효하지 않아요.
procedure TList.Sort(compare: TCompareFunc);
Var
_t : integer;
begin
// do something.
end;
- 로컬 타입 블록에는
TCompareFunc타입 하나가 있어요. 제네릭 클래스 정의 안에서는 실제 타입이 아직 알려지지 않아요. 정의에는 자리표시자_T에 대한 참조만 들어 있죠. 그 외의 모든 식별자 참조는 제네릭 클래스가 특수화될 때가 아니라 정의될 때 알려져 있어야 해요.
로컬 변수 블록은 아래와 동일한 의미예요.
generic TList<_T>=class(TObject)
Public
type
TCompareFunc = function(const Item1, Item2: _T): Integer;
Public
data : _T;
procedure Add(item: _T);
procedure Sort(compare: TCompareFunc);
end;
- 선언과 구현에서 타입 파라미터 이름은 같아야 해요.
제네릭 클래스뿐 아니라 다른 타입도 정의할 수 있어요. 아래는 레코드, 함수 타입, 배열 타입, 인터페이스, 클래스, 배열 별칭까지 다양한 제네릭 정의를 보여주는 예시예요.
{$mode objfpc}
{$INTERFACES CORBA}
type
generic PlanarCoordinate<t> = record
x,y : t;
end;
TScreenCoordinate = specialize PLanarCoordinate<word>;
TDiscreteCoordinate = specialize PlanarCoordinate<integer>;
TRealCoordinate = specialize PlanarCoordinate<extended>;
generic TDistanceFunction<t> = function (x,y : t) : Extended of object;
TScreenDistance = specialize TDistanceFunction<word>;
TDiscreteDistance = specialize TDistanceFunction<integer>;
TRealDistance = specialize TDistanceFunction<Extended>;
generic TArray<t> = array of t;
TMyIntegerArray = specialize TArray<integer>;
generic IList<_T> = Interface
Function GetItem(AIndex : Integer) : _T;
Procedure SetItem(AIndex : Integer; AValue : _T);
Function GetCount : Integer;
Property Items [AIndex : Integer] : _T Read GetItem Write SetItem;
Property Count : Integer Read GetCount;
end;
generic TList<_T>=class(TObject, specialize IList<_T>)
public type
TCompareFunc = function(const Item1, Item2: _T): Integer;
Function GetItem(AIndex : Integer) : _T;
Procedure SetItem(AIndex : Integer; AValue : _T);
Function GetCount : Integer;
Public
data : _T;
procedure Add(item: _T);
procedure Sort(compare: TCompareFunc);
end;
generic TPointSet<t> = array of specialize PlanarCoordinate<t>;
TScreenPointSet = specialize TPointSet<word>;
TDiscretePointSet = specialize TPointSet<integer>;
TRealPointSet = specialize TPointSet<extended>;
주석: 가시성에 관해 한 마디. 템플릿 타입 T나 _T는 strict private 타입으로 제공돼요. 즉 이 타입들은 파생 클래스에서 사용할 수 없어요. protected나 private 같은 메커니즘으로 접근 가능하게 만들지 않는 한 그래요. 아래 예시처럼요.
generic TList<_T>=class(TObject)
public type
TItemType = _T;
end;
더 알아보기
- 제네릭을 특수화하는 방법은 8.3 절에서 다뤄요.
- 제네릭 타입에 제약을 거는 방법은 8.4 절에서 확인할 수 있어요.
- 제네릭이 왜 2단계 과정인지는 8.1 절(제네릭 소개)을 참고해요.