Jump to content
과거의 기술자료(읽기 전용): https://tech.devgear.co.kr ×
과거의 기술자료(읽기 전용): https://tech.devgear.co.kr

사용자 정의 매니지드 레코드(Managed Record) - 10.4 시드니에서 델파이 언어에 추가됨


Recommended Posts

마르코 칸투 (Marco Cantu)"Custom Managed Records New in Delphi 10.4 Sydney" 를 번역했습니다. (원문 작성: 2020년 5월, 번역 업데이트:  2023년 7월)

Table of Contents


간략(40초) 비디오

 

델파이에서 매니지드 레코드(Managed Record), 즉 관리되는 레코드란?

델파이의 레코드는 필드를 가질 수 있는데, 어떤 데이터 타입도 필드가 될 수 있다. 레코드 안에 일반 필드(예: 숫자, Enum 등)만 있으면 컴파일러는 별로 할 일이 없다. 레코드를 생성하고 폐기하는 일은 그저 메모리 할당과 할당했던 메모리 영역 해제를 할 뿐이다. (기본 설정에서, 델파이는 레코드를 0으로 초기화 하지 않으니 주의하자) 

레코드 안에 있는 필드가 만약 컴파일러에 의해 관리되는 타입이라면 (예: String, Interface), 컴파일러는 추가 코드를 넣는다. 초기화와 종료화를 관리하기 위해서이다. 예를 들어, String(문자열)은 참조 카운트가 적용되는 타입이다. 그런데 String을 담고 있는 레코드가 생존 범위(scope)를 벗어나면, 그 레코드 안에 있던 String에 대한 참조 카운트(reference count)는 줄어야 한다. 그래야 그 String에게 할당되었던 메모리가 해제(deallocate)될 수 있다. 다시 말해, 우리가 코드에서 매니지드 레코드를 사용하면, 컴파일러는 그 코드를 감싸는 try-finally 블록을 자동으로 추가한다. 그렇기 때문에, 예외(exception)가 발생해도 레코드 안의 데이터가 메모리에 남지 않는다고 우리가 확신할 수 있다. 이것은 이미 오래 전부터 있었다. 즉, 매니지드(관리되는) 레코드는 델파이 언어에 이미 있던 기능이다.

 

초기화 연산자와 최종화 연산자를 가지는 레코드 (Records with Initialize and Finalize Operators)

10.4부터, 델파이 레코드 타입은 사용자 지정(custom) 초기화(initialization)와 종료화(finalization)를 지원한다. 이 초기화와 종료화는 컴파일러가 매니지드 레코드를 위해 수행하는 기본 작업들에 더해 추가로 수행된다. 레코드의 필드에 사용되는 데이터 타입이 무엇이든 상관없이, 우리는 레코드를 선언하면서 사용자 지정 초기화와 종료화 코드를 레코드에 넣을 수 있다. 또한 초기화와 종료화의 구현 코드는 우리가 직접 작성한다. 이렇게 하려면, 새로 추가된 특정 연산자들을 레코드 타입에 추가하면 된다 (원한다면, 그 중 하나만 추가해도 된다). 

다음은 간단한 예제이다.

type
  TMyRecord = record
    Value: Integer;
    class operator Initialize (out Dest: TMyRecord);
    class operator Finalize(var Dest: TMyRecord);
  end;

물론 위 두 클래스 메서드(class method)를 정의하는 코드는 직접 작성해야 한다. 예를 들어, 메서드 실행을 로그(log/기록)에 남기거나 또는 레코드의 값을 초기화할 수 있다. - 아래 예문은 Value 필드를 초기화하고, 이 레코드를 담은 메모리 위치에 대한 참조를 로그로 남겨서 어느 레코드가 이 동작을 수행하는지를 기록한다.

class operator TMyRecord.Initialize (out Dest: TMyRecord);
begin
  Dest.Value := 10;
  Log('생성됨' + IntToHex (Integer(Pointer(@Dest)))));
end;

class operator TMyRecord.Finalize(var Dest: TMyRecord);
begin
  Log('파괴됨' + IntToHex (Integer(Pointer(@Dest)))));
end;

위 생성(construction) 방식과 레코드에서 예전부터 제공되던 생성 방식의 차이는 자동 호출(automatic invocation)이다. 아래 예문처럼 코드를 작성하면, 초기화 코드와 최종화 코드가 모두 호출된다. 그리고 우리가 만든 매니지드 레코드 인스턴스에는 try-finally 블록이 자동으로 추가된다 (컴파일러가 알아서 반영함).

procedure LocalVarTest;
var
  my1: TMyRecord;
begin
  Log (my1.Value.ToString);
end;

위 코드를 실행하면 아래와 비슷한 로그를 얻는다 (주소는 다를 것이다).

생성됨 0019F2A8
10
파괴됨 0019F2A8

또 다른 상황은 인라인 변수(inline variable)를 사용할 때이다. 아래 코드와 같다.

begin
  var t: TMyRecord;
  Log(t.Value.ToString);

위 코드를 실행하면, 앞에서 본 것과 똑같은 순서로 로그가 생성된다.

 

할당 연산자 (The Assign Operator)

할당 연산자(:=)는 레코드 필드의 모든 데이터를 그대로(flatly) 복사한다. 할당 연산자의 기본 동작이 타당하기는 하지만, 당신이 사용자 정의 (custom) 데이터 필드와 사용자 정의 초기화를 만들었다면, 아마도 기본(default) 동작을 변경하고 싶었기 때문일 것이다. 이런 이유로, 사용자 정의 매니지드 레코드에서는 할당 연산자(assignment operator)를 우리가 직접 정의하는 것도 가능하다. 할당 연산자를 호출할 때는 := 구문(syntax)을 사용하지만, 정의할 때는, Assign을 사용한다.

 class operator Assign (var Dest: TMyRecord; const [ref] Src: TMyRecord);

이 연산자 정의는 규칙을 매우 정확하게 지켜야 한다. 즉, 첫 번째 파라미터는 참조로 전달 (pass by reference) 되는 즉 var 파라미터이고, 두 번째 파라미터는 참조로 전달 (pass by reference) 되는 const 파라미터이어야 한다. 그러지 않으면, 컴파일러는 다음과 같은 에러 메시지를 표시한다.

[dcc32 Error] E2617 First parameter of Assign operator must be a var parameter of the container type
[dcc32 Hint] H2618 Second parameter of Assign operator must be a const[Ref] or var parameter of the container type

Assign 연산자를 부르는 예문은 다음과 같다.

var
  my1, my2: TMyRecord;
begin
  my1.Value := 22;
  my2 := my1;

위 코드를 실행해 얻은 로그는 다음과 같다 (각 레코드에 순번을 붙여주도록 위 코드에 추가한 결과임).

생성됨 5 0019F2A0
생성됨 6 0019F298
5 복사하여 6 넣음
파괴됨 6 0019F298
파괴됨 50019F2A0

파괴 순서는 생성 순서와 반대라는 점을 눈 여겨 보자.

 

매니지드 레코드를 파라미터로 전달하기 (Passing Managed Records as Parameters)

파라미터로 전달되거나, 함수에 의해 반환되는 경우에도, 매니지드 레코드의 동작은 일반 레코드와 다를 수 있다. 아래 루틴(routine)들은 이런 다양한 상황을 보여준다.

procedure ParByValue (rec: TMyRecord);
procedure ParByConstValue (const rec: TMyRecord);
procedure ParByRef (var rec: TMyRecord);
procedure ParByConstRef (const [ref] rec: TMyRecord);
function ParReturned: TMyRecord;

일일이 살펴볼 필요 없이, 정보를 요약하면 다음과 같다

  • ParByValue 메서드는, 새 레코드를 생성하고 (사용할 수 있다면) 할당 연산자를 불러 데이터를 복사하여 넣는다. 메서드가 범위를 벗어나면 이 복사본은 삭제된다.
  • ParByConstValue 메서드는, 복사도 하지 않고 호출도 하지 않는다.
  • ParByRef 메서드는, 복사도 하지 않고 호출도 하지 않는다.
  • ParByConstRef 메서드는, 복사도 하지 않고 호출도 하지 않는다.
  • ParReturned 메서드는, (초기화를 통해) 새 레코드를 생성한다. 생성된 새 레코드는 이 메서드 안에서만 유지된다. my1 := ParReturned 와 같이 할당하는 코드가 있다면, Assign 연산자를 호출하여 반환한다. 생성됐던 새 임시 레코드는 삭제된다.

 

예외와 매니지드 레코드 (Exceptions and Managed Records)

일반적으로 레코드는 예외(exception)가 발생하는 경우에 깨끗이 지워진다. try-finally 블록을 명시적으로 적지 않아도 제거된다는 점은 매니지드 레코드가 오브젝트와 근본적으로  다른 점이며, 이것이 정말 유용하게 사용되는 주된 이유이다.

procedure ExceptionTest;
begin
  var a: TMRE;
  var b: TMRE;
  raise Exception.Create('Error Message');
end;

이 프로시저는 생성자를 두 번 호출하고, 소멸자도 두 번 호출한다. 다시 말하지만, 이것이 매니지드 레코드의 근본적인 차별점이자 핵심 기능이다.

 

매니지드 레코드들을 담는 배열 (Arrays of Managed Records)

매니지드 레코드(managed record)들을 담는 정적 배열(static array)을 정의하면, 선언 시점에 안에 들어가는 레코드들이 초기화된다. 그 시점에 initialize 연산자가 호출되기 때문이다.

var
  a1: array [1..5] of TMyRecord; // call here
begin
  Log ('ArrOfRec');

배열이 범위를 벗어나면 그 안의 레코드들은 모두 소멸된다. 매니지드 레코드들을 담는 동적 배열(dynamic array)을 정의하는 경우에는, 초기화 코드가 호출되는 시점이 동적 배열의 크기를 지정하는 (SetLength 함수) 호출을 하는 시점이다.

var
  a2: array of TMyRecord;
begin
  Log ('ArrOfDyn');  
  SetLength(a2, 5); // call here

 

결론

10.4에서 델파이 언어에 추가된 훌륭한 새 기능인 "사용자 정의 매니지드 레코드"를 간략하게 소개했을 뿐이다. 예를 들어, 매니지드 레코드는 제네릭 레코드(generic record)에서 효과적으로 활용된다. 뿐만 아니라 여기에서는 언급하지 않았지만, 많은 다양한 상황에서 활용할 수 있다. 언어에 새로 도입된 새 기능 중에서 1등이지만, 그 외에도 모든 플랫폼에서 메모리 관리를 단일화하는 등 다른 기능들도 있다.

이 댓글 링크
다른 사이트에 공유하기

매니지드 레코드에서 초기화(initalization)와 종료화(finalisation) 자동으로 호출하는 기능을 활용하여 INI 파일을 다루는 간단하고 좋은 예제와 설명을 제공한 글이 있어서 링크를 남깁니다. 

이 댓글 링크
다른 사이트에 공유하기

이 토의에 참여하세요

지금 바로 의견을 남길 수 있습니다. 그리고 나서 가입해도 됩니다. 이미 회원이라면, 지금 로그인하고 본인 계정으로 의견을 남기세요.

Guest
이 토픽(기고/질문)에 답하기

×   서식있는 텍스트로 붙여넣기.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   이전에 작성한 콘텐츠가 복원되었습니다..   편집창 비우기

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...

중요한 정보

이용약관 개인정보보호정책 이용규칙 이 사이트가 더 잘 작동하기 위해 방문자의 컴퓨터에 쿠키가 배치됩니다. 쿠키 설정 변경에서 원하는 설정을 할 수 있습니다. 변경하지 않으면 쿠키를 허용하는 것으로 이해합니다.