본문 바로가기
NC University/Advanced C++

6일차 - checked delete

by 날쑤 2015. 10. 21.

Intro

  일반적으로 헤더 파일 등에서 다른 유저 정의 타입 A의 포인터 변수만 사용되는 경우에는 아래와 같이 타입 A를 전방선언해서 사용한다. 이처럼 완전한 선언없이 전방선언만 존재하는 타입의 포인터를 불완전 객체(incomplete object)라고 한다.

  불완전 객체는 타입에 대한 정보가 전혀 없기 때문에 delete 시에 객체의 소멸자가 호출되지 않는다는 문제점이 있다. 다음의 코드를 통해서 이러한 상황을 확인해보자.

  코드를 수행하면 생성자에 의한 출력만 나온다. 만약 foo 함수가 Test 클래스의 정의보다 아래에 위치한다면(즉, Test 타입의 정보를 안다면) 소멸자는 정상적으로 호출될 것이다. 하지만 foo 함수가 여전히 잠재적인 버그를 내포하고 있다는 사실은 변하지 않는다. 그래서 C++ 관용구(idiom) 중에는 위와 같은 상황에서 차라리 컴파일 에러를 발생시킴으로써 문제를 빠르게 검출할 수 있도록하는 checked delete라는 기법이 존재한다.

checked delete

  이 기법은 불완전한 타입에는 컴파일 타임 연산인 sizeof를 할 수 없다는 점을 이용한다. 즉, 위의 foo 함수에서 포인터를 delete 하기 전에 대상 타입인 Test 타입에 대해 sizeof를 적용함으로써 불완전 객체일 경우에는 컴파일 에러를 발생시키도록 하는 것이다. 아래의 코드에서 확인할 수 있듯이 적용방법 자체는 매우 심플하다.

  이제는 p가 완전한 타입의 객체일 경우에는 delete가 정상적으로 수행되고, 불완전 객체일 경우에는 컴파일러가 컴파일 에러를 발생시키면서 p가 불완전 객체임을 알려준다. 여기서 sizeof 연산을 enum으로 감싼 부분이 좀 특이한데, 이것은 특정 컴파일러의 경우에는 sizeof만 사용하면 코드 최적화 과정에서 이를 무시하고(즉, 컴파일 에러를 발생시키지 않고) 그냥 넘어갈 수 있기 때문에 이를 방지하기 위함이다.

  참고로 boost 라이브러리에는 foo 함수와 유사한 기능을 하는 checked_delete 함수 템플릿이 존재한다. 이 함수 템플릿은 foo 함수와 기본 아이디어 자체는 별 차이가 없다. 하지만 이 함수 템플릿은 아래와 같이 sizeof 값을 enum이 아닌 배열의 크기를 결정하는데 사용하고 있다.

  이는 어떤 컴파일러는 타입 T가 incomplete할 때 sizeof 연산이 컴파일 에러를 발생시키지 않고 0을 리턴하도록 구현되어 있을 수 있기 때문이다. 이러한 컴파일러 위에서 foo 함수는 기존의 문제를 그대로 가진다. 그렇기 때문에 boost에서는 배열은 0 이하의 크기를 가질 수 없다는 점을 이용해서 불완전 객체가 함수의 인자로 들어오는 경우에는 컴파일러에 무관하게 항상 컴파일 에러가 발생하도록 checked_delete 함수 템플릿을 구현하고 있다.