프로그래밍/cpp

[c++] 스마트 포인터 (auto_ptr) 에 대한 이해

콘파냐 2015. 4. 14. 08:38

이번 포스팅은 스마트 포인터(auto_ptr)에 대해서 알아보겠습니다.


C++11에선 (스마트 포인터)shared_ptr이 표준에 추가되었다. 이는 참조 카운팅을 추가하여 스마트 포인터간의 매끄러운 대입을 가능하게 하였다. 스마트포인터의 종류에 대한 설명은 다음 포스팅으로 미루고 여기에선 스마트포인터의 원리에 관해서 원론적인 이야기를 할까 한다.


내용에 들어가기 앞서 스마트 포인터에 관한 이해를 위해서는 다음과 같은 주제에 대한 이해가 필히 요구된다.

템플릿, 연산자 오버로딩, 정적할당, 동적할당, 스택, 힙, 생성자,파괴자...



2014/06/16 - [프로그래밍/c++] - C++ 템플릿, 함수템플릿에 대한 이해


2014/06/17 - [프로그래밍/c++] - 클래스 템플릿에 대한 이해 C++


2014/06/18 - [프로그래밍/c++] - 비타입 인수,디폴트 템플릿 인수,특수화 (클래스,함수 템플릿) c++



2014/03/19 - [프로그래밍/c++] - 연산자 오버로딩 C++



2013/05/09 - [프로그래밍/c++] - 생성자와 소멸자 호출 C++


2014/04/21 - [프로그래밍/c++] - c++ 생성자 소멸자/(virtual)가상 소멸자를 쓰는 이유


c++을 모른다면 생성자와 소멸자(파괴자)에 대해 생소하게 생각할 수 있다. 이 글은 c++에 관한 글이므로  c언어를 공부중이라면 그냥 이런게 있구나 하고 이해 정도는 해주자.


c언어에서 동적할당과 메모리 해제를 malloc/free 로 한다면 c++은 new/delete 가 있다. c++에서 모든 타입의 동적할당은 다음과 같은 표현으로 가능하다.

Type* A = new Type();

(c++에서는 구조체, 클래스도 하나의 타입으로 인정하고 위와 같이 동적할당을 할 수 있다.)


int형 포인터 변수 a는 메모리의 스택영역에 생성되었고 4의값을 가지는 int  타입의 데이타가 힙 영역에 할당된다. a포인터는 이 값을 가리킨다.

point클래스형 포인터 A도 스택영역에 할당되었고, 힙영역에는 실질적인 point객체가 생성되었다.

이 값들은 main 함수 영역내에서 생성된 것이며 프로그램이 종료될 때 메모리에서 해제가 되어야 한다. 위 예처럼 delete 구문을 주석처리를 해놓으면 main함수 종료 시 스택영역에 할당된 a,A 포인터만 메모리 해제가 되고 힙영역 데이타는 해제되지 않는다. 이렇게 힙영역에 할당된 메모리를 해제하지 않으면, 시스템에서는 아직 사용중으로 인식하여 이 부분의 메모리를 사용을 하지 않는다. 메모리누수(memory leak)의 원인인 것이다. 포인터와 동적할당 객체는 단지 주소값으로만 연결되어 있는 위태로운 상황이다. 포인터가 없어지면 실질적인 값만 남게 된다. 더 이상 사용되어지지 않는 실질적인 값을 가비지(garbage)라 불린다. 이 값은 하나의 사용 가능한 정보 데이타지만, 프로그램 입장에선 더 이상 의미없는 정보이고, 메모리 누수(memory leak)을 일으킨다. 


자바의 유명한 가비지 컬렉션(garbage collection)은 이렇게 더 이상 사용되어지지 않는 메모리를 주기적으로 정리하는 기능입니다. 우리의 c++은 그딴 거 없습니다. 무조건 프로그래머의 몫입니다. 그런데,


힙영역에 동적할당된 메모리를 해제하기 위해선 delete만 쓰면 간단히 해결된다.


이렇게 간단하게 해결할 수 있는 문제에 굳이 스마트 포인터라는 것까지 등장하게 된 이유는 무엇일까?

첫째, 이런 메모리 누수는 프로그램에 치명적이지 않다. 물론 에러발생도 하지 않는다. 이런 이유로 이런 메모리 해제 작업을 소홀히 할 수 있다.

둘째, 예기치 않은 에러 등으로 프로그램이 종료될 경우 힙영역에 아직 가동중인 데이타의 해제가 제대로 일어나지 않을 수 있다. 또한 장기간 실행되어야 하는 서버 프로그램의 경우 작은 메모리 누수도 메모리 관리에 치명적으로 작용할 수 있다.

이런 문제점들을 개선하기 위해 스마트 포인터를 도입한 듯 하다.(이건 제 주관적인 견해입니다.)


스마트 포인터의 원리

다음은 이 원리를 이해하기 위한 내용이다.


1.함수내의 지역변수는 그 함수의 반환 시 같이 스택 내에서 해제된다. 

2.객체의 경우 메모리에서 해제가 일어나면 파괴자(소멸자)를 호출하는데 파괴자(소멸자)의 기본 역할은 생성자가 생성한 메모리를 해제하는 역할을 한다. 생성자의 역할은 객체의 생성 시 멤버변수의 초기화를 담당한다.


다음은 스마트 포인터를 흉내낸 템플릿 클래스다.


모든 타입에 가능한 사용을 위해 템플릿으로 만들었고, 단순히 할당과 해제만 가능하도록 만들었다. int형 값 3이 동적 할당되어 smpt 클래스 객체인 test의 멤버 변수가 이 값을 가리키도록 초기화 되었다.(smpt 생성자가 동적할당된 객체의 포인터를 인수로 받아들인다.) 프로그램이 종료되고 main 함수 반환시 test 객체는 스택에서 해제된다. 객체가 메모리에서 해제될 때 파괴자(소멸자)는 자동으로 호출되므로 파괴자 내부에 delete p;가 작동하여 자동으로 동적 할당된 객체가 해제된다. 잘보면 test는 포인터가 아니다. 요점은 smpt 클래스의 객체 test변수는 스택에 할당된 값이므로 함수 리턴 시 자동으로 해제되고, 포인터가 아닌 객체 자체이므로 해제될 때 파괴자를 호출하여 delete p;를 언제나 호출한다는 것이다.


auto_ptr 의 정의(memory 헤더파일)

template <typename T> auto_ptr class {...}


auto_ptr의 사용

auto_ptr<T> test(new T(.,.))


그냥 사용법만 외우고 사용만 해도 상관은 없겠다. 그래도 이왕 살펴 봤으니 좀 더 깊게 살펴보려고 한다.


말은 스마트 포인터지만, 실제로 포인터는 아니다. 구체적으로 말하면, 포인터를 래핑한 포인터의 래퍼클래스라고 말 할 수 있다.

실제 포인터가 아닌 스택에 저장되는 객체 변수이므로 이를 이용하여 파괴자 호출을 통한 delete의 안정적인 호출을 할 수 있지만, 객체변수라서 생기는 문제점을 보완해야 한다.


다음 코드를 분석하여 문제점을 보완해 나가겠다.


x,y좌표값을 멤버변수로 갖는 point 클래스를 선언하고 포인터 클래스로 객체를 생성하여 좌표값을 출력하려 하였다. 다음과 같은 컴파일 에러가 발생한다.


스마트 포인터의 목적은 달성하였지만, 주석처리된 코드처럼 사용하려면 연산자에 대한 오버로딩이 필요하다. 실제로 a는 point클래스의 포인터가 아닌 래퍼 클래스 객체변수이기 때문이다.


2014/03/19 - [프로그래밍/c++] - 연산자 오버로딩 C++


연산자 오버로딩에 대한 문법은 위 글을 참조하시길 바랍니다.




->, * 연산자 오버로딩

T* operator->() const {return T}

a->print();  // a.operator->()->print(); (사실, 이 부분은 잘 이해가 가지는 않는다. 그냥, 이렇게 해석되는구나 하고 넘어감)

위와같이 해석되어 a.operator->() 이 T가 되어야 함.


* 연산은 그냥 이해하면 된다.


이 외에도 auto_ptr은 더 많은 연산자 오버로딩을 가질 것이다.  이 부분은 추후에 좀더 살펴보도록 하겠다.

반응형