DSC_0435

Advertisements

링크드 리스트(Singly Linked List) – (2) 삭제(deletion)

지난 번 주어진 링크드 리스트에 새 정수를 삽입하는 것을 살펴 보았습니다. 주의할 점 중에 빠뜨린 것은, 헤드 포지션에 삽입된 헤드를 업데이트 하기 위해서 InsertList 함수가 더블 포인터를 취한다는 정도 입니다.

LIST_STATUS InsertList(node **head, int newData);

이번에는 주어진 정수를 링크드 리스트에서 찾아서 삭제하는 것을 생각해 봅시다. 링크드 리스트를 다룰 때 ‘현재 포지션의 엘리먼트’를 삭제하거나 ‘현재 포지션의 엘리먼트 전에‘ 삽입하려면 이전 엘리먼트의 포인터를 저장하고 있어야 합니다. 이전 엘리먼트의 포인터를 저장하는 변수를 추가로 선언하고 싶지 않다면 ‘현재 포지션의 다음 엘리먼트‘를 삭제하거나 ‘현재 포지션 엘리먼트의 다음에‘ 삽입하는 식으로 생각하면 됩니다.

현재 엘리먼트(cur)를 삭제하는 경우는 prev가 필요합니다:

[prev] -> [cur] -> [next] ,cur를 삭제 후에는 [prev] -> [next]

현재 엘리먼트의 다음(next) 엘리먼트를 삭제하는 경우:

[cur] -> [next] -> [next of next] ,next를 삭제 후에는 [cur] -> [next of next]

prev 변수를 사용하지 않고 링크드 리스트에서 주어진 정수를 찾아 삭제하는 것을 생각해 봅니다. (1)헤드 포지션에 삭제할 경우는 헤드 엘리먼트의 next를 헤드로 정해주고 헤드 엘리먼트를 free해 주면 되겠습니다. (2) 테일 포지션이나 중간에서 삭제할 경우는 cur->next를 cur->next->next로 할당해 주고 cur->next를 삭제하면 되겠습니다. 테일에서 삭제할 경우 cur->next->next는 NULL이 됩니다. 바로 코딩을 해 볼까요?

LIST_STATUS DeleteList(node **head, int delData)
{
  node *curItem, *tmpItem;

  // sanity check
  if(head == NULL)
    return LIST_STATUS_ERROR;

  // empty list. do nothing
  if(*head == NULL)
    return LIST_STATUS_SUCCESS;

  curItem = *head;
  // delete from head
  if(curItem->data == delData)
  {
    *head = curItem->next;
    free(curItem);
    return LIST_STATUS_SUCCESS;
  }

  while(1)
  {
    if((curItem->next) && (curItem->next->data == delData))
    {
      tmpItem = curItem->next->next;
      free(curItem->next);
      curItem->next = tmpItem;
      break;
    }

    if(curItem->next == NULL)
      break; // data is not in the list

    curItem = curItem->next;
  }

  return LIST_STATUS_SUCCESS;
}

링크드 리스트(Singly Linked List) – (1) 삽입(insert)

테크니컬 인터뷰에서 가장 많이 등장하는 것이 링크드 리스트일 듯 싶습니다. 링크드 리스트의 인기는, 비교적 복잡하지 않으면서, C의 포인터에 대한 개념도 들어가 있고, 예외 처리나 바운더리 컨디션 또한 고려해야 되기 때문에 짧은 시간 안에 인터뷰어의 여러가지를 볼 수 평가해 볼 수 있기 때문입니다. 링크드 리스트 패밀리 중에, 가장 기초적인 문제라면 “정수값을 가지는 링크드 리스트가 있다. 여기서 새 정수를 삽입하라.” 정도 입니다. 이 문제를 버벅이거나 실수하게 되면 인터뷰이에게 안 좋은 인상을 주게 되겠지요.

먼저 문제에 함정이 없는지 살펴 봅시다. 링크드 리스트니 일단 사이즈에 대해서는 고려 안해도 될 거 같고, 32-bit 머신일 경우 signed integer는 -(2^31)부터 2^32-1까지, unsigned integer의 경우는 0부터 2^32-1까지 수가 될 수 있을 겁니다. 문제를 풀 도메인을, 32-bit 머신과 정수는 signed integer라고 해 둡시다. 그 외 다른 함정은 없어 보이나요? 이미 링크드 리스트에 있는 숫자가 또 주어지는 경우는요? 우리는 이 경우 주어진 정수를 버리기로 합시다.

그럼 다음 단계는 코딩을 시작하기 전에 예제를 가지고 시뮬레이션해보는 겁니다. 주어진 링크드 리스트와 새 정수가 다음과 같다고 해 봅시다.

1 -> 5 -> 10 -> 12 -> 30       새 정수: 11

앞에서 부터 현재 아이템과 비교해서 새 정수가 크면 다음 아이템으로 갑니다. 만약 다음 아이템 보다 현재 정수가 작으면 그 자리에 새 정수를 넣으면 되고, 아니면 현재 아이템을 다음 아이템으로 업데이트 한 다음 같은 비교를 반복하는 것입니다. 이 것을 슈도 코드로 만들어 봅시다.

loop:
  If (curItem->data == newData) OR (curItem->next->data == newData)
    return duplicated
  Else if (curItem->data < newData) AND (curItem->next->data > newData)
    Then insert newItem between curItem and curItem->next
  Else
    curItem = curItem->next
    goto loop

코딩까지 내려가려면 ‘insert newItem between curItem and curItem->next’를 좀 더 자세하게 써봐야할 필요가 있겠습니다. (1)새 노드를 만들고 (2)새 노드의 데이터에 정수를 할당합니다. (3)새 노드의 next 포인터에 현재 노드의 next포인터를 할당합니다. (4)현재 노드의 next 포인터는 새 노드가 되겠죠.

(1) newItem = new node
(2) newItem->data = newData
(3) newItem->next = curItem->next
(4) curItem->next = newItem

이제 node 스트럭처를 정의해 봅시다. 별다른 것은 없으나 스트럭처의 멤버 중 next를 자신을 가르키는 포인터로 선언한다는 것 정도 보면 되겠습니다. 물론 next를 void로 선언해도 괜찮습니다.

typedef struct _node
{
  int data;
  struct _node *next; // This can be declared as void, such as void *next
} node;

지금까지 설명한 것을 모아서 함수 InsertLst를 작성해 봅시다.

typedef struct _node
{
  int data;
  struct _node *next;
} node;

enum
{
  LIST_STATUS_SUCCESS,
  LIST_STATUS_ERROR,
} LIST_SATATUS;

LIST_STATUS InsertList(node** head, int newData)
{
  node *newItem, *curItem;

  // invalid argument
  if(head == NULL)
    return LIST_STATUS_ERROR;

  newItem = (node *)malloc(sizeof(node));
  if(newItem ==NULL)
    return LIST_STATUS_ERROR;
  newItem->data = newData;
  newItem->next = NULL;

  // empty list
  if(*head == NULL)
  {
    *head = newItem;
    return LIST_STATUS_SUCCESS;
  }

  curItem = *head;

  // insert to head point
  if(curItem->data > newData)
  {
    *head = newItem;
    newItem->next = curItem;
    return LIST_STATUS_SUCCESS;
  }
  while(1)
  {
    if((curItem->data == newData) || 
       ((curItem->next) && (curItem->next->data == newData)))
    {
      free(newItem); // duplicate. throw it away
      break;
    }

    if((curItem->data < newData) &&
       (!curItem->next || // insert in tail
        ((curItem->next) && (curItem->next->data > newData)))) // insert in mid
    {
      newItem->next = curItem->next;
      curItem->next = newItem;
      break; // successfully inserted
    }

    curItem = curItem->next;
  }

  return LIST_STATUS_SUCCESS;
}

함수를 다 작성한 후에는 테스트 셋을 몇 개 만들어 유닛 테스트를 시뮬레이션 해 봅니다. 이 때 바운더리 컨디션을 테스트할 수 있도록 테스트 셋을 만들면 좋습니다. 즉 (1)주어진 링크드 리스트가 NULL인 경우 (2)링크드 리스트의 헤드 포지션에 삽입하는 경우 (3)링크드 리스트의 중간에 삽입하는 경우 (4)링크드 리스트의 테일 포지션에 삽입하는 경우 (5)중복된 정수가 주어진 경우에 대해 각각 테스트 셋을 만들어서 시뮬레이션을 돌려 봅니다.

UMDF101 – 사용자 모드 드라이버 구조 이해하기 (2/3)

This posting written in Korean is translated from ‘UMDF 101 – Understanding User Mode Driver Frameworks’ published in The NT Insider. (이 글은 The NT Insider에 실렸던 ‘UMDF 101 – Understanding User Mode Driver Frameworks’을 번역한 두번째 글입니다.)


주: 이 글을 읽기 전에 첫번째 글을 읽으세요 – UMDF101 – 사용자 모드 드라이버 구조 이해하기 (1/3)

UMDF 드라이버 구조

앞서 이야기한 것 처럼, UMDF는 COM 기반의 DLL입니다. 그러나 UMDF는 COM 런타임 라이브러리를 사용하지 않습니다. 대부분의 COM 개발자에게 이 COM 런타임 라이브러리는 골치덩어리이기 때문에 이것은 좋은 소식이죠. UMDF는 컨커런시(concurrency, 역자 주: 동시성? 컴퓨터 사이언스에서 컨커런시란 연산이나 실행이 동시에 일어나는 것을 말합니다. 한국어로는 뭘로 번역해야 할지 애매하네요. 컨커런시를 지원해야 한다면 공유되는 데이터를 프로텍트하는 것이나 레이스컨디션등을 고려해야 합니다.)를 제어하거나 로드 언로드하기 위해서 COM을 사용하지 않습니다. UMDF는 COM을 프로그래밍 패턴으로만 이용합니다. COM 인터페이스를 C++ 앱스트랙트 베이스 클래스라고 쉽게 생각하면 되겠습니다.

앱스트랙트 베이스 클래스로서, UMDF는 KMDF 개발자라면 친숙할 클래스들을 가집니다:

  • IWDFDriver
  • IWDFDevice
  • IWDFFile
  • IWDFIoQueue
  • IWDFIoRequest
  • IWDFIoTargget
  • IWDFMemory
  • IWDFObject

이 이름들은 KMDF 모델과 거의 같습니다. 이것은 KMDF 모델에 익숙한 개발자들이 쉽게 UMDF 모델을 쉽게 익힐 수 있도록 하기 위한 것입니다. KMDF에서와 같이 UMDF 드라이버 작성자는 오브젝트를 지원할 콜백(COM에서는 인터페이스라고 하죠.)들을 선택합니다. 그 인터페이스와 연관된 이벤트가 발생했을 때 UMDF 프레임웍은 UMDF 드라이버 안에 있는 루틴(COM에서는 메쏘드라고 하는)들을 부릅니다.

앞에서 이야기 했듯이, UMDF는 UMDF 오브젝트들을 표현하는 클래스들을 가지고 있습니다. 이 UMDF 오브젝트들은 지원하는 콜백들의 시그너처를 인터페이스의 형태로 제공합니다. UMDF 드라이버를 구현하는 것은 이 인터페이스들을 제공하는 것이라고 할 수 있습니다. 이 인터페이스들은 퓨어 버츄얼 함수들이여서 인터페이스를 지원하는 UMDF 드라이버는 그 콜백의 인터페이스가 가지는 모든 함수들을 구현해야 합니다. 그렇지 않으면 드라이버는 컴파일 되지 않을 것 입니다. 이것을 COM 용어로 이야기 해보자면, 여러분은 UMDF 클래스를 베이스로 하는 COM 오브젝트를 생성할 것이고, 원하는 기능을 제공하기 위해서 인터페이스/메쏘드들을 제공할 것입니다.

여러분이 알아야할 또 다른 COM에 관계된 것은 COM 오브젝트들은 모두 IUnknown이라는 인터페이스를 지원한다는 것입니다. 이 인터페이스는 COM 오브젝트의 사용자들이 어떤 오브젝트에 의해서 지원되는 다른 인터페이스들의 포인터를 얻도록 하고, 이 인터페이스 포인터들을 관리할 수 있도록 합니다. IUnknown 인터페이스는 다음 세 개의 메쏘드를 지원합니다:

  1. AddRef
  2. Release
  3. QueryInterface

AddRef와 Release 메쏘드는 COM 기반 오브젝트의 레퍼런스 카운터를 더하거나 빼는데 사용됩니다. 이 레퍼런스 카운터는 COM이 이 오브젝트가 사용 중인지 아닌지 알 수 있도록 합니다. 만약 레퍼런스 카운터가 0이 되면, COM은 이 오브젝트를 언로드할 수 있다는 것을 알게 될 것 입니다. 커널 모드 드라이버 작성자에게 이 레퍼런스 카운터는 I/O 매니저가 관리하는 드라이버 오브젝트의 레퍼런스 카운터와 다를 것이 없습니다.

 QueryInterface 메쏘드를 이해하는 것은 중요한데요, 이것은 쿼리된 오브젝트가 특정 인터페이스를 지원하는지 판단할 때 사용되기 때문입니다. 콜러(caller)는 찾고자 하는 인터페이스 아이디(IID, Interface Identifier)를 함께 넘겨서 쿼리를 던집니다. 콜리(callee)가 이 인터페이스를 지원하면 인터페이스의 어드레스와 함께 응답하게 됩니다. QueryInterface가 성공하게 되면 언제던지 오브젝트의 레퍼런스 카운터가 증가하게 되므로, 그 오브젝트 인터페이스가 더 이상 필요하지 않으면 콜러는 반드시 레퍼런스 카운터를 Release해야 합니다. 그러므로 UMDF가 여러분의 드라이버 오브젝트가 지원하는 인터페이스를 찾아내는 방법은 QueryInterface를 통해서 그 오브젝트들을 쿼리하는 것 입니다.

 

UMDF101 – 사용자 모드 드라이버 구조 이해하기 (1/3)

This posting written in Korean is translated from ‘UMDF 101 – Understanding User Mode Driver Frameworks’ published in The NT Insider. (이 글은 The NT Insider에 실렸던 ‘UMDF 101 – Understanding User Mode Driver Frameworks’을 번역한 첫번째 글입니다.)


아마 여러분은 커널 모드 드라이버 프레임웍(KMDF, Kernel Mode Driver Framework)에 대해 많이 들어봤을 겁니다. 그러나 사용자 모드 드라이버 프레임웍(UMDF, User Mode Driver Framework)에 대해서는 들어본 적이 있나요? 이 글에서는 UMDF를 소개하고 그 구조에 대해 간단히 훓어보고자 합니다.

아직 UMDF는 활발하게 개발 중이라는 것을 이 글을 읽을 때 염두에 두시구요. 이 글 전체가 바뀔 수도 있습니다.사실 여기 있는 어떤 것들은 벌써 잘못된 것일 수도 있어요. 그러니 여러분이 UMDF 드라이버를 작성할 때는 꼭 문서를 자세하게 확인하세요. 알았죠? (역자 주: 원글은 2009년에 작성되었습니다..)

들어가기

UMDF는 USB 같은 프로토콜 버스 장치들을 지원하기 위한 드라이버들을 사용자 모드로 작성하기 위한 새로운 방식입니다. 현재 지원되는 장치들은:

  • PDA나 휴대폰과 같은 이동 저장 장치
  • 포터블 뮤직 플레이어
  • USB 벌크(bulk) 전송 장치
  • 부가 디스플레이/비디오 장치

마이크로소프트는 이런 종류들의 드라이버들을 사용자 모드로 옮김으로써, 드라이버의 작성을 간편하게 하고 Windows OS의 전체적인 안정성을 향상할 수 있다고 생각합니다. 사용자 모드는 에러나 버그 등에 좀더 너그러운 환경(역자 주: 예를 들어 커널 모드 드라이버가 유효하지 않은 포인터를 참조하는 오류를 일으키면 블루 스크린과 함께 시스템이 크래쉬 되겠지만, 유저 모드에서 실행되는 어플리케이션은 그 어플리케이션만 크래쉬되는 것으로 끝난다. )이라는 점에서 저는 이 말에 동의합니다. 하지만 냉정하게 생각해 봅시다. 만일 여러분이 커널 모드 프로그램의 복잡성을 고려할 필요 없는, 잘못 동작했을 경우 사용자의 시스템에 크래쉬를 일으키게 할 수도  있는 위험에 대해 걱정할 필요가 없는 드라이버를 개발할 수 있다면, 여러분의 커스터머나 마이크로소프는 더 만족해 할 것입니다.

사용되는 오브젝트들의 관점에서 보면 UMDF 드라이버는 KMDF 모델과 비슷하고 약간의 차이점이 있습니다. 사실 UMDF는 COM-Lite라고 불리는 COM(Component Object Model)을 사용합니다.

COM에 기반한 드라이버라구요! 처음 UMDF가 COM에 기반한 솔루션이 될 것이라는 것을 처음 들었을 때 저는 무서웠습니다. 이전에 몇 번의 COM에 대한 경험이 모두 안 좋았기 때문입니다.(그나마 좋게 말해서 말이지요.) COM 문서들은 쉽게 읽히지 않았고, COM을 개발하기 위한 툴들은 사용하기 좋지 않았습니다.(역자 주: 저만 COM에 대한 나쁜 기억이 있는게 아니었군요.) 물론 제 주 업무는 커널 모드 드라이버를 작성하는 것이기 때문에 COM에 대해서는 전문가가 아닙니다. 그러니 그냥 ‘이게 잘 될까?’하는 불길한 생각 정도가 들었다고 해 둡시다. 저는 제 장치들을 제어하는 드라이버를 작성하기를 원하는 것이지 COM 전문가가 되길 원하지는 않았습니다.

그러나, UMDF와 예제들을 테스트해보고 나서는 저는 이게 그렇게 나쁘지는 않다는 것을 받아들여야 했습니다. 익숙해지기 위해서는 아마 시간이 좀 걸릴지는 모르겠지만, 크게 볼 때 대부분의 개발자들이 간단한 USB 드라이버를 편하게 개발할 만한 모델이라는 생각이 들었습니다.

UMDF 구조

이름이 얘기하듯이 UMDF 드라이버는 사용자 모드에서 동작합니다. 구체적으로 말하자면 WUMDFHost.exe라는 실행 이미지가 동작하는 프로세스 안에서 동작합니다. UMDF의 일반적인 구조는 그림1과 같습니다.


그림 1 – UMDF의 기본적인 구조(linked from original article http://www.osronline.com/article.cfm?id=449)

UMDF 드라이버는 DLL(Dynamic Link Library)로 구현됩니다. 이 DLL은 COM 프로그래밍 패턴을 사용하여 표준적인 COM 엔트리 포인트들을 내놓습니다. 드라이버는 C나 C++로 작성할 수 있고 Win32 API는 물론 더 많은 COM 지원을 위해서 ATL(Active Template Library)도 사용할 수 있습니다. UMDF 드라이버를 이론적으로는 C로 작성할 수 있지만 현재 C 예제는 없습니다. 드라이버와 COM에 대해서 다음 장에서 좀 더 얘기해 보죠.

현재 UMDF 구조에서는 WUMDFHost 프로세스 당 디바이스의 수는 하나로 제한 되어 있습니다. 만약 UMDF 드라이버를 지원하는 세 개의 동일한 장치들이 시스템에 장착된다면, 각각의 장치 당 세 개의 WUMDFHost 프로세스들이 있게 되겠지요. 이 세 개의 드라이버들은 서로 다른 프로세스 안에 있기 때문에, 여러분은 이 프로세스들 간에 데이터를 공유하기 위한 여러분 만의 방법을 구현해야만 합니다.

드라이버를 호스팅하는 WUMDFHost 프로세스에 더하여 UMDF “시스템”의 한 부분인 또 다른 사용자 모드 프로세스가 있습니다. 이 두번째 프로세스는 여러분의 드라이버가 동작하는 WUMDFHost와 시스템 안에 있는 다른 WUMDFHost 프로세스와 통신하는 UMDF 드라이버 매니저 서비스(UMDF Driver Manager Service)입니다. 장치가 시작됨과 함께 시작되는 이 서비스는 호스트 프로세스의 일생에 대한 책임과 UMDF 커널 모드 리플렉터 드라이버(UMDF Kernel Mode Reflector Driver)로 부터 오는 메시지에 응답할 책임이 있습니다.

커널 모드로 내려가서, UMDF 커널 모드 리플렉터 드라이버(또는 간단히 리플렉터)는 사용자 모드와 커널 장치 스택 간에 필요한 연결을 제공합니다. 각가의 커널 UMDF 장치 스택 당 하나의 리플렉터 장치 인스턴스가 존재 합니다. 이 리플렉터는  I/O를 전달하고 커널 모드로 부터 호스트 프로세스로 피엔피(PnP) 메시지를 전달하는 책임이 있습니다.

어떻게 UMDF 드라이버에 보내지는사용자 I/O가 처리될까요? 그림 1에서 처럼, 사용자 모드 응용 프로그램은 어떤 장치에 대한 요구로서 커널 모드에 리퀘스트를 보냅니다. 커널 모드 안에서 리플렉터는 리플렉터가 사용자 모드에서 동작하고 있는 해당하는 WUMDFHost 프로세스에게 그 리퀘스트를 보냅니다. 응용 프로그램은 이 리퀘스트의 과정에 대해서는 아무 것도 알 필요가 없습니다. – 이 과정을 아는 UMDF 드라이버는 사용자 모드에서 동작하고 있구요.

…계속

디스트럭터를 버츄얼로 선언해야 할 때는 언제인가?

The following article written in Korean is translated from ‘When should your destructor to be virtual?’ by Raymond Chen. Raymond’s blog provides machine translation to Korean, but many of output are not even in correct form of syntax. Overall it’s hard to read. So I decided to translate it to help myself and who want to read it in Korean. Thanks Raymond for his brilliant articles.  (이 글은 Rymond Chen의 ‘When should your destructor to be virtual?’을 제가 번역한 글임을 밝힙니다.)


C++ 오브젝트의 디스트럭터(destructor)를 버츄얼(virtual)로 선언해야 될 때는 언제 일까요?

먼저, 버츄얼 디스트럭터를 선언한다는 것은 무슨 의미일까요?

흠, 그렇다면 버츄얼 메쏘드(method)는 무엇을 의미하는 것일까요?

메쏘드가 버츄얼이면, 오브젝트의 그 메쏘드를 호출하는 것은 상속 트리에서 제일 하위에 있는 클래스에 구현된 메쏘드가 항상 불리게 됩니다. 이 메쏘드가 버츄얼이 아니면 컴파일될 때 그 오브젝트 포인터가 가르키는 구현이 불리게 되지요.

예 하나를 들어 볼까요?

class Sample {
 public:
 void f();
 virtual void vf();
};

class Derived : public Sample {
public:
 void f();
 void vf();
}

void function()
{
 Derived d;
 Sample* p = &d;
 p->f();
 p->vf();
}

p->f()는 Sample::f를 부르게 됩니다. p가 Sample을 가르키는 포인터이기 때문이지요. 실제 오브젝트는 Derived로 선언되었지만, 여기서 포인터 p는 단지 Sample을 가르키는 포인터입니다. f가 버츄얼이 아니기 때문에 포인터의 타입이 사용되었습니다.

반면에 p->vf()는 Derived::vf를 부르게 됩니다. 왜냐하면 vf는 버츄얼이기 때문에 상속 트리에서 가장 아래에 있는 Derived::vf가 불립니다.

자 이제 여기 까지 이해가 되었습니다.

버츄얼 디스트럭트는 이와 같은 방식으로 동작합니다. 디스트럭터를 코드에 넣어서 직접 호출하는 경우는 거의 없을 것입니다. 그렇게 하기 보다는 오브젝트가 스코프에서 사라질 때나 오브젝트를 델리트(delete)할 때 디스트럭터가 불려질 것입니다.

void function()
{
 Sample* p = new Derived;
 delete p;
}

Sample은 버츄얼 디스트럭터를 가지고 있지 않기 때문에, delete p는 포인터가 가르키는 클래스의 디스트럭터(Sample::~Sample)를 호출합니다. 상속된 Derived::~Derived가 불리지 않는 것 입니다. 그리고 여러분이 볼 수 있듯이, 이 경우는 잘못된 것입니다.

자 여기까지 잘 이해하셨다면, 여러분은 처음 질문에 답할 수 있을 겁니다..

다음 두 조건들을 만족하는 클래스는 반드시 버츄얼 디스트럭터를 가져야 합니다:

  • delete p를 할 경우
  • 실제로 p가 상속된 클래스의 포인터를 가르킬 가능성이 있을 때.

몇 사람들은 여러분이 버츄얼 메쏘드를 가질 경우에만(if and only if) 버츄얼 디스트럭트가 필요하다고 합니다. 그러나 이것은 모든 방향으로 다 잘못된 것입니다.

다음은 클래스가 버츄얼 메쏘드를 가지지 않지만 버츄얼 디스트럭트가 필요한 경우입니다:

class Sample { };
class Derived : public Sample
{
 CComPtr m_p;
public:
 Derived() { CreateStreamOnHGlobal(NULL, TRUE, &m_p); }
};

Sample *p = new Derived;
delete p;

delete p는 Derived::~Derived 대신 Sample::~Sample을 부를 것이고, 결과적으로 stream m_p가 메모리에 남겨질(leak) 것입니다.

그리고 여기에 클래스가 버츄얼 메쏘드를 가지지만 버츄얼 디스트럭터가 필요하지 않은 경우의 예가 있습니다.

class Sample { public: virtual void vf(); }
class Derived : public Sample { public: virtual void vf(); }

Derived *p = new Derived;
delete p;

오브젝트를 삭제할 때 사용하는 포인터가 실제 오브젝트의 타입과 맞기 때문에, 올바른 디스트럭터가 불리워질 것입니다. 이런 경우는 COM 오브젝트에서 자주 일어납니다. 오브젝트가 자신이 가지고 있는 인터페이스에 몇 개의 버츄얼 메쏘드를 가지고 있지만, 오브젝트가 베이스 클래스 포인터에 의해서 아니고 그 자신의 구현에 의해서 삭제되는 경우입니다.(버츄얼 디스트럭터를 가지고 있는 COM 인터페이스는 없다는 것을 알아두세요.)

여기서 문제는, 버츄얼 디스트럭터를 만들 것인가 말것인가를 결정하기 위해서는 다른 사람들이 여러분의 클래스를 어떻게 사용할 것인가를 알아야 한다는 것입니다. 만약 C++가 “sealed” 키워드를 가지고 있다면 규칙은 간단해 지겠습니다만: 만일 여러분이 “delete p”를 하고 여기서 p가 unsealed 클래스의 포인터라면 그 클래스는 버츄얼 디스트럭터가 필요합니다.(상상의 “sealed” 키워드는 어떤 클래스가 다른 클래스를 위한 베이스 클래스의 역할을 할 수 있다는 것을 명확히 표시하기 위해 사용합니다.)