Skip to content

2014 10 14 스마트 포인터

krikit edited this page Nov 26, 2014 · 1 revision

스마트 포인터

스마트 포인터란 동적 할당한 메모리를 해제하지 않아 생기는 메모리 누수를 방지하기 위해 고안된 템플릿 기반의 편리한 포인터 wrapper 클래스를 말합니다. 아래 코드는 얼핏 봐서는 메모리 누수가 없을 것 같지만 do_something() 메소드에서 예외가 발생하면 메모리 누수가 생깁니다.

MyClass obj = new MyClass();
obj->do_something();
delete obj;

스마트 포인터는 스택 스코프를 벗어나면서 로컬 변수가 자동으로 해제되는 점을 이용해 구현된 포인터의 wrapper 클래스입니다. 아래 코드는 스마트 포인터의 사용 예입니다.

std::unique_ptr<MyClass> ptr(new MyClass());
ptr->do_something();

함수가 리턴되면서 로컬 변수인 ptr의 메모리가 해제되며 unique_ptr 클래스의 소멸자가 자동으로 호출됩니다. 이때 MyClass 클래스의 소멸자를 자동으로 호출하게 되어 메모리 누수로부터 안전합니다.

스마트 포인터를 사용하면 함수의 리턴 전에 함수에서 생성한 모든 메모리를 해제한다거나, 예외 발생에 따라 메모리를 해제해주는 번거로움으로부터 자유로와지며, 혹시 모를 실수로 인해 메모리 누수가 발생하는 경우를 줄여줄 수 있으므로 사용을 강력히 추천 드립니다.

스마트 포인터의 종류

스마트 포인터는 세가지 종류가 있습니다. shared_ptr, unique_ptr, auto_ptr입니다. C++11 이전에 만들어진 auto_ptr는 STL과 사용시 안전하지 않아 조만간 deprecate될 예정이라고 하니 꼭 사용하셔야 한다면 주의를 하셔야 합니다.

C++11부터 도입된 shared_ptr와 unique_ptr는 참조 카운팅을 하는지 여부에 따라 다릅니다. shared_ptr는 내부에 참조 횟수에 대한 카운터를 두어 대입, move 등의 연산 시 카운터를 증가시키거나 감소시킵니다. wrapper 클래스의 소멸자에서 최종적으로 참조 카운트가 0이 될 때 실제 객체의 소멸자가 비로소 호출됩니다.

따라서, 묻지도 따지지도 않고 모든 경우에 그냥 shared_ptr를 사용하는 선택은 중박 이상은 됩니다. ^^;

double free 문제

스마트 포인터 사용 시 일반 포인터와의 혼용은 재앙을 불러일으킬 수 있습니다. 아래 코드는 double free가 발생합니다.

MyClass* obj = new MyClass();
std::shared_ptr<MyClass> ptr1(obj);
std::shared_ptr<MyClass> ptr2(obj);

ptr1과 ptr2가 각각 스택에서 소멸되면서 delete를 호출하게 되어, ptr2의 소멸자에서 crash가 일어나겠죠. 스마트 포인터 사용 시에는 아래와 같이 일반 포인터 사용은 자제하고 대입 연산자를 쓰는 것이 정석입니다.

std::shared_ptr<MyClass> ptr1(new MyClass());
std::shared_ptr<MyClass> ptr2 = ptr1;

메모리 누수 감지

리눅스나 Posix 계열의 경우 valgrind라는 훌륭한 툴이 있지만 Visual C++의 경우 별도의 툴이 필요 없이 아래와 같은 두가지 조치로 가능하다고 합니다.

  1. 메모리 누수가 의심되는 코드의 헤더 삽입 부분
#define _CRTDBG_MAP_ALLOC
#include <stdlib.h>
#include <crtdbg.h>
  1. 메인 함수의 마지막 부분
_CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);

또한 다음 코드를 추가하면 해당 코드의 n번째 메모리 할당 시점에 break point를 설정할 수 있습니다.

_CrtSetBreakAlloc(n);
Clone this wiki locally