C++ - 스마트포인터

Claire·2024년 9월 19일

스마트포인터(Smart Pointer)

1. 자동파괴자 auto_ptr이란

포인터처럼 동작하지만 스스로 메모리를 관리하기 때문에 메모리 누수를 방지하고 메모리 관리의 복잡성을 줄여주는 C++의 특별한 클래스 타입

스마트 포인터는 C++에서 제공하는 포인터의 한 형태로, 포인터가 가리키는 메모리를 자동으로 해제하는 역할을 한다.

일반적으로 변수에 동적 메모리를 할당하면 그 메모리를 직접 해제해야한다. 그렇지 않으면 메모리 누수가 발생하게 되고, 이는 프로그램의 성능을 저하시킬 수 있을 뿐더러, 해제를 했다 하더라도 그 메모리를 다시 사용하려고 하는 '잘못된 해제(dangling reference)'문제를 발생시킬 가능성도 있다.

이러한 문제를 해결하기 위한 것이 "스마트 포인터(smart pointer)"로, 객체 생성 시 사용했던 동적 메모리 또는 시스템 자원을 소멸 시 자동으로 소멸할 수 있는 메커니즘을 제공함으로써 범위를 벗어난 변수는 스택에서 제거되며, 객체의 파괴자가 호출되어 자신이 사용하던 자원을 알아서 정리하여 메모리 누수를 방지해준다.

2. auto_ptr의 필요성

void main() {
	double* rate = new double; // 포인터 변수 생성 및 new 연산자로 8바이트 힙메모리 생성
	// 선언 시 사용하는 *는 rate가 포인터 변수라는 것을 알림
	*rate = 3.14;
	// 포인터변수가 가리키는 실제 메모리 주소에 들어있는 값을 가리키는 *

	cout << *rate << endl;
	delete rate;
}

위 예제 코드에서 rate 변수는 동적으로 생성한 실수형 변수의 메모리를 저장하는 포인터 변수이다.
rate 변수의 동적 메모리를 헤재하는 delete 수행을 주석 처리하면 rate 변수는 종료 시에 자동으로 메모리가 소멸되지만, 이 변수가 가리키는 주소 메모리는 자동으로 해제되지 않는다.

즉, 메모리 누수(Memory Leak)가 발생한다. 이렇듯, 동적으로 할당된 메모리는 이름이 없으므로 포인터를 잃어버리면 참조할 수 없어 해제가 어렵게 되기 때문에 스마트 포인터를 통해 이러한 문제를 방지한다.

3. auto_ptr의 사용방법

  • auto_ptr은 동적으로 할당된 메모리도 자동으로 해체하는 포인터의 레퍼 클래스
  • auto_ptr은 템플릿으로 구성되어 있으며, momory 헤더 파일에 정의되어 있으므로 사용 시, 헤더를 선언 후 다음과 같이 정의한다.
template<typename T> class auto_ptr

포인터가 가리키는 대상체 타입 T를 인수로 받아 T*형의 포인터를 관리하며, 생성자로 전달한 포인터는 소멸자에서 delete로 해제하므로 포인터 뿐만 아니라 포인터가 가리키는 메모리도 자동으로 해제된다.

4. auto_ptr의 내부구조

#include<iostream>
#include<memory>

void main() {
	auto_ptr<double> rate(new double); 
	*rate = 3.14;
	cout << *rate << endl;
}

위 코드에서 rateauto_ptr의 객체이다. 그런데, 원래 우리가 아는 포인터 클래스에서는 rate는 포인터 변수였다. 여기서 rate가 포인터 변수가 아닌 객체라면 new double로 생성한 포인터 변수는 어디에 있을까?

이건 auto_ptr의 내부로 들어가면 알 수 있다.

auto_ptr의 내부 구조

template <class _Ty>
class auto_ptr { // wrap an object pointer to ensure destruction
public:
    using element_type = _Ty;

    explicit auto_ptr(_Ty* _Ptr = nullptr) noexcept : _Myptr(_Ptr) {}

    auto_ptr(auto_ptr& _Right) noexcept : _Myptr(_Right.release()) {}

    auto_ptr(auto_ptr_ref<_Ty> _Right) noexcept {
        _Ty* _Ptr   = _Right._Ref;
        _Right._Ref = nullptr; // release old
        _Myptr      = _Ptr; // reset this
    }
    
    ~~ 중간 생략~~
    
    ~auto_ptr() noexcept {
        delete _Myptr;
    }

auto_ptr의 내부구조 코드를 보면 결국 new double로 생성한 객체에 대한 주소값을 가리키는 포인터 변수는 rate이 아닌 내부의 _Myptr변수 인 것을 알 수 있다.
또한, 내부 코드에서 이미 소멸자를 통해 main()함수가 끝나는 시점에 해당 포인터 변수를 delete함으로써, 메모리를 해제하고 있음을 알 수 있다.

따라서, 위 코드에서 생성한 rate를 따로 delete해주지 않아도 되는 것이며, 만약 따로 delete를 선언하면 에러가 발생하는 이유이기도 하다.

4-1. 스마트포인터 만들어보기

template <class T>
class MySmartPointer {
private:
	T* p;

public:
	MySmartPointer(T* sp) {
		this->p = sp;
	};
	~MySmartPointer() { delete this->p; }
};

void main() {
	MySmartPointer<string> str(new string("test"));
}

여기서 "test"라는 데이터를 가진 str객체를 생성할 때, 해당 데이터의 메모리 주소값은 sp가 받는다. 그런데 sp는 멤버함수에서 this->p를 받고 있다. 결과적으로 "test"라는 데이터의 주소값을 받고있는 것은 p인 것이고, 그래서 소멸자에서 delete this->p로 메모리를 str 종료 시점에서 자동 삭제하고 있는 것이다.

5. shared_ptr과 auto_ptr의 비교

shared_ptr이란

boost:shared_ptr로 C++ 표준 라이브러리가 아닌 boost에서 만든 스마트 포인터로 auto_ptr의 단점을 보완하기 위해 생겨난 스마트포인터이다. 사용 형태는 auto_ptr과 동일하다.

profile
SEO 최적화 마크업 개발자입니다.

0개의 댓글