Delphi 2009(Win32) 에서 추가된 Generics 에 관해서 간략하게 써 보겠습니다.

 

Generic 이란 임의의 타입에 대해 동작하는 매소드(클래스) 를 만드는 방법으로, C++에서는 Template이라 불리며 C#, Java 등에서는 Generics 라 불리는 기능이며, 일반화 프로그래밍을 가능하게 해주는 방법입니다.

 

간단한 예제를 하나 만들어 보겠습니다

변수의 크기를 출력하는 간단한 예제입니다.

 

 

program Project1;

 

{$APPTYPE CONSOLE}

 

uses

  SysUtils, VarUtils;

 

type

  TempClass = Class

  public

    procedure GetSize<T>(Item : T);

  end;

 

  TempRecord = packed Record

    A : Integer;

    B : Double;

    C : Double;

  End;

 

procedure TempClass.GetSize<T>(Item : T);

begin

  WriteLn('Size : ', Sizeof(Item));

End;

 

var

  A : TempClass;

  B : Integer;

  C : TempRecord;

begin

  A.GetSize(3000);

  A.GetSize(4.0);

 

  B := 30;

  A.GetSize(B);

 

  A.GetSize(C);

  ReadLn;

end.

 

 

결과는 각각

2, 10, 4, 20 이 나옵니다.

 

여기서 즉 T 이라는 임의의 Type 에 관해서 크기를 알아 올 수 있는겁니다.

 

여기서 어떻게 Type 이 지정 되냐를 알 수 있는데.

3000을 넘었을 경우는 2byte 정수형 으로 인정한 것을 알 수 있습니다.

 

정수형의 경우는 입력한 숫자의 크기에 따라 1~8 byte 정수형으로 함수가 생성 됩니다.

4.0을 넣었을 경우는 크기가 10이 나왔는데, 모든 실수형은 Extended 형으로 생성합니다.

 

Integer형의 변수 B를 넣었을 경우는 이미 타입이 Integer  라는 것을 명시적으로 선언 하였으므로 크기는 4Byte 가 됩니다.

 

TempRecord 형의 변수는 당연히 20Byte가 나옵니다(Integer + Double + Double)

 

이처럼 하나의 함수로 다양한 Type 에 대한 계산이 가능해 집니다.

 

이것은 컴파일 시점에 GetSize 라는 함수를 프로그래머가 사용한 Type 에 따라 여러벌 만들기에 가능한 것입니다.

 

디스어셈블한 코드를 구경 해보도록 하겠습니다

Project1.dpr.22: WriteLn('Size : ', Sizeof(Item));

0040E1BB A1980A4100       mov eax,[$00410a98]

0040E1C0 BAF0E14000       mov edx,$0040e1f0

0040E1C5 E86A73FFFF       call @Write0UString

0040E1CA BA0A000000       mov edx,$0000000a

0040E1CF E89856FFFF       call @Write0Long

0040E1D4 E8BF56FFFF       call @WriteLn

0040E1D9 E8724DFFFF       call @_IOTest
Project1.dpr.22: WriteLn('Size : ', Sizeof(Item));

0040E200 A1980A4100       mov eax,[$00410a98]

0040E205 BA30E24000       mov edx,$0040e230

0040E20A E82573FFFF       call @Write0UString

0040E20F BA04000000       mov edx,$00000004

0040E214 E85356FFFF       call @Write0Long

0040E219 E87A56FFFF       call @WriteLn

0040E21E E82D4DFFFF       call @_IOTest

Project1.dpr.22: WriteLn('Size : ', Sizeof(Item));

0040E240 A1980A4100       mov eax,[$00410a98]

0040E245 BA70E24000       mov edx,$0040e270

0040E24A E8E572FFFF       call @Write0UString

0040E24F BA14000000       mov edx,$00000014

0040E254 E81356FFFF       call @Write0Long

0040E259 E83A56FFFF       call @WriteLn

0040E25E E8ED4CFFFF       call @_IOTest

 

 

이와 같이 같은 이름의 함수가 비슷비슷한 코드로 여러벌 생성 되어 메모리에 들어있는 것을 확인 할 수 잇습니다.

 

 

이번엔 Type 을 명시적으로 알려주는 코드를 보도록 하겠습니다.

A.GetSize(3); 의 호출 결과는 1이었습니다.

숫자가 작기떄문에 최대한 작은 크기의 메모리를 소모 하기 위하여 Byte 형으로 선언 된 결과입니다.

 

그런데 간혹 정해진 Type 이 필요 할 수도 있습니다.

이럴경우 Type 을 명시적으로 지정해서 호출 해 보겠습니다.

 

A.GetSize<Integer>(3);

로 코드를 변경 한 후 호출하면 4가 출력 되는 것을 알 수 있습니다.

 

무조건 Integer 형의 함수를 호출 하라는 일종의 협박(?) 비슷한 코드를 만들어 낸 셈이 되는겁니다.

 

 

 

다음으로 알아 볼 것은 모든 알고리즘이 임의의 타입에 대해 동작 될 수 없는 경우에 관한 해결책입니다.

 

위의 코드에서 Integer, Double, Record 형의 크기는 아무 문제 없이 구할 수 있었지만, String 형의 크기는 sizeof() 연산자로 구할 수 없습니다.

 

이럴경우 String에 관해서만 특별한 처리를 해 주어야 하는데이는 익히 잘 아시는 오버로딩을 응용하면 됩니다.

 

예제코드를 보겠습니다.

program Project1;

 

{$APPTYPE CONSOLE}

 

uses

  SysUtils, VarUtils;

 

type

  TempClass = Class

  public

    procedure GetSize(Item : String); overload;

    procedure GetSize<T>(Item : T); overload;

  end;

 

  TempRecord = packed Record

    A : Integer;

    B : Double;

    C : Double;

  End;

 

procedure TempClass.GetSize<T>(Item : T);

begin

  WriteLn('Size : ', Sizeof(Item));

End;

 

procedure TempClass.GetSize(Item : String);

begin

  WriteLn('Size : ', Length(Item));

End;

 

var

  A : TempClass;

  B : Integer;

  C : TempRecord;

begin

  A.GetSize<Integer>(3);

  A.GetSize(4.0);

 

  B := 30;

  A.GetSize(B);

 

  A.GetSize(C);

  A.GetSize('Hello Delphi!');

  ReadLn;

end.

 

GetSize 매소드를 T 형과 String 형에 관해 각각 오버로딩 하엿습니다.

String 형으로 GetSize 를 호출하면 Length 를 호출해서 계산하는 함수가, 다른 형으로 GetSize를 호출하면 Sizeof 를 사용하는 함수가 호출 됩니다.

 

이처럼 특정한 Type 에 관해 매소드를 다른 형태로 사용 할 수 있습니다.

 

 

지금까지는 함수의 Generic 을 살펴 보았습니다. 이제 클래스에 관한 Generic 을 살펴 보도록 하겠습니다.

 

program Project1;

 

{$APPTYPE CONSOLE}

 

uses

  SysUtils, VarUtils;

 

type

  TempClass<T> = Class

  public

    Item : T;

  end;

 

  TempRecord = packed Record

    A : Integer;

    B : Double;

    C : Double;

  End;

 

var

  A : TempClass<String>;

  B : TempClass<Integer>;

  C : TempClass<TempRecord>;

begin

  A := TempClass<String>.Create;

  A.Item := 'Hello RAD Studio!';

 

  B := TempClass<Integer>.Create;

  B.Item := 512;

 

  C := TempClass<TempRecord>.Create;

  C.Item.A := 55805096;

  C.Item.B := 3.14159;

  C.Item.C := 0.0;

  ReadLn;

end.

 

위 코드에서는 T 라는 임의의 타입을 가지는 클래스 TempClass<T> 를 선언하엿습니다.

그리고 각각 : String, Integer, TempRecord 형을 가지는 클래스 TempClass<String>. TempClass<Integer>, TempClass<TempRecord> 를 선언 하엿습니다 (TempClass<타입명> 까지가 클래스 이름이라고 생각하시면 편합니다)

 

그리고 각각의 클래스를 생성하고 Item 변수에 접근하여 값을 넣었습니다.

선언한 형태에 따라 TempClass 는 여러 타입의 Item 을 가지는 클래스로 탄생합니다.

 

TIntegerList, TDoubleList 등등을 따로 제작 할 필요 없이 임의의 타입을 가지는 콜렉션을 만들어 쓸 수 있습니다. 사실 이게 Generic을 쓰는 가장 큰 이유라고도 할 수 있습니다. Generic 으로 콜렉션을 만들면 캐스팅이 필요 없고, 타입이 명확하기 때문에 편리하게 사용 할 수 있습니다.

(.net Dictionary<> , STL Vactor<>, List<>, MFC CArray<,> 등이 그에 해당합니다)

마지막으로 한가지만 더 보도록 하겠습니다.

program Project1;

 

{$APPTYPE CONSOLE}

 

uses

  SysUtils, VarUtils;

 

type

  TempClass<T1, T2> = Class

  public

    Item1 : T1;

    Item2 : T2

  end;

 

var

  A : TempClass<String, Integer>;

begin

  A := TempClass<String, Integer>.Create;

  A.Item1 := 'Hello RAD Studio!';

  A.Item2 := 3;

 

  ReadLn;

end.

 

임의의 Type 은 반드시 한 개여야 한다는 제약은 없습니다.

여러 개의 임의의 Type 을 가지는 클래스도 얼마든지 선언 가능합니다.

 

위 코드는 2개의 임의의 Type을 가지는 TempClass<,> 를 선언, 사용 한 모습입니다.

 

 

PS. 간단한 함수도 매소드로 만들어서 예제를 구성했는데... 제가 잘 못해서 그런건진 모르겠는데, 델파이의 제네릭은 전역함수의 제네릭화를 허용 하지 않는 것 같습니다.

 

----

Who's Lyn

profile

글 목록 보기

Lyn
조회 수 38340 추천 수 0 댓글 1
?

단축키

Prev이전 문서

Next다음 문서

크게 작게 위로 아래로 댓글로 가기 인쇄
?

단축키

Prev이전 문서

Next다음 문서

크게 작게 위로 아래로 댓글로 가기 인쇄
?
  • ?
    GomSun2 2010.06.17 11:24

    와우 좋은 강좌 잘봣음. :)


Board Pagination Prev 1 ... 4 5 6 7 8 9 10 11 12 13 Next
/ 13