본문 바로가기

BackEnd/C랑 C++

C++ 스마트 포인터를 알아보자 - (1) unique_ptr

C++에는 아주 유용한 개념이 있다

메모리 관리를 할 때마다 고려해야할 점은 바로 "메모리 누수"인데,

그것에 대해 조금은 자유롭게 생각할 수 있는 스마트 포인터라는 것이다

 

가장 자주 사용하는건 shared_ptr이었는데,

여러 개가 있는건 이유가 있다는 생각이 들어서

하나씩 정리하려고 한다

(모던 C++ 입문 교재를 참고해서 작성하였습니닥)

 


1. 스마트 포인터에는 뭐가 있지?

스마트 포인터는 3가지가 있다

 

1) unique_ptr (오늘할거)

2) shared_ptr

3) weak_ptr

 

요렇게 3가지 인데, 참고로 스마트 포인터는 C++ 11에서 도입된 개념이다

C++ 03에는 auto_ptr라는 것도 있다고 한다 -_-a 하지만 삭제됨 쓰지마셈

 

1-1) 스마트 포인터 헤더는?

 

#include <memory>
or
#include "boost/shared_ptr.hpp"

 

기본 STL에서 제공하는 스마트 포인터는 <memory> 헤더에서 쓸 수 있다

근데 C++11에서 제공하는 기능인데, C++ 11을 사용할 수 없을 수도 있다...

그럴 때는 Boost에 있는걸 사용하면 된다고 한다

 

1-2) 그냥 포인터를 쓰면 안되는건가?

써도 된다 ㅋ

스마트 포인터를 사용시에 큰 장점은 사용하는 포인터가 만료되면

메모리가 자동으로 해제되기 때문에 메모리 관리 차원에서 편하다

 

하지만 구현하는데는 조금 까다롭고 유지보수가 어려울 수 있으니까

잘 알고 사용하자 ㅋ

 

2. unique_ptr

이름에서 느껴지는가... 뭔가 유니크하다

유니크할 것 같은 느낌이 든다..

 

unique_ptr은 포인터를 통해 Unique한 소유권을 가지고

unique_ptr 범위를 벗어날 때 해당 개체를 처리하는 스마트 포인터이다

 

바로 사용 예시를 보겠다

 

#include <memory>
#include <iostream>

using namespace std;

int main(void)
{
    std::unique_ptr <int> ptr(new int(10));

    cout << *ptr << endl;

    return 0;
}

 

메모리에 대한 포인터에 대해서는 new로 생성 해줬고,

포인터이기 때문에 *ptr로 값을 출력을 해준 예제이다

 

해당 포인터는 만료되면 메모리가 자동으로 해제될 것이다

메모리는 자동으로 해제되지만, 메모리를 동적으로 할당해주지 않았다면

core가 발생하거나 오류가 발생할 수 있다

 

2-1) unique_ptr이 unique한 이유

unique_ptr은 유니크!!! 고유한!!! 포인터라는 이름을 갖고 있는 이유가 있다..

왜냐면, 다른 포인터 타입에 할당되거나 변경할 수 없다

 

데이터를 다른 값으로 바꾼다고 한다거나,

다른 unique_ptr 변수에 할당하려고 한다!? 하면

아래와 같은 에러가 뜰 것이다

 

"error: use of deleted function ‘std::unique_ptr<_Tp, _Dp>::unique_ptr(const std::unique_ptr<_Tp, _Dp>&) [with _Tp = int; _Dp = std::default_delete<int>]"

 

포인터가 가르키는 데이터를 얻고 싶다면 get()이라는 함수를 사용하면 된다

바로 요렇겜

 

#include <memory>
#include <iostream>

using namespace std;

int main(void)
{
    std::unique_ptr <int> ptr(new int(10));
    cout << *ptr << endl;

    int* raw_ptr = ptr.get();
    cout << *ptr << endl;

    return 0;
}

 

unique_ptr은 다른 unique_ptr에 할당하는 것도 안된다

 

int main(void)
{
    std::unique_ptr <int> ptr(new int(10));
    cout << *ptr << endl;
	
    /* 에러 발생 */
    std::unique_ptr <int> ptr_cp{ptr};
    cout << *ptr_cp << endl;

    return 0;
}

 

위에서 언급한 같은 에러가 날 것이다 ㅋ

unique_ptr은 이동만 할 수 있다

이동은 무슨 함수냐... move() 다

 

std::unique_ptr <int> ptr_cp{move(ptr)};

 

바로 이렇게 ㅋ

move() 불필요한 복사를 피하려고 트릭을 사용하는 문법인데

 

그림을 빌려 설명하자면

ptrA에 Song 객체의 소유권이 부여되고,

move를 하면 그 소유권을 그대로 ptrB에 넘겨주게 된다

ptrA는 nullptr이 된다ㅋ

 

정리하자면 unique_ptr은 데이터를 복제하는 복사(copy)는 안돼요

원래 주소에서 데이터를 전송하는 이동(move)은 돼요

→ 고유한 소유권이 있기 때문이다

 

2-2) unique_ptr을 생성하는 방법은 new뿐인가?

아니다ㅋ

물론 이렇게도 많이 쓰는데, C++ 14 부터 제공하는 make_unique()를 쓰면

더 안전하고 코드에 통일성을 줄 수 있다

 

int main(void)
{
    std::unique_ptr <int> ptr = make_unique<int>(10);
    cout << *ptr << endl;

    return 0;
}

바로 이렇게ㅋ 

new로 사용하는게 습관이 되었는데, make 함수를 사용하도록 해야겠당

 

2-3) unique_ptr을 return 받으면 소유권은?

크게 생각안해도 된다

예시로 바로 긔긔

std::unique_ptr<int>
unique_func()
{
    return std::unique_ptr<int>{new int(10.0)};
}

int main(void)
{
    auto ptr = unique_func();
    cout << *ptr << endl;

    return 0;
}

 

요렇게 되면 unique_ptr을 함수에서 반환할 때

메모리의 소유권을 전달하기 때문에, 이 경우에는 move를 하지 않아도 된다

 

2-4) 메모리 영역을 지우고 싶다면?

스마트 포인터를 사용하지 않을 경우에는

알아서 delete가 된다고 알고 있지만... 메모리 영역을 내가 지우고 싶을 수도 있지 않은감

그럴때는 reset()이라는 함수를 사용하면 된다

 

int main(void)
{
    std::unique_ptr <int> ptr(new int(10));
    cout << *ptr << endl;

    std::unique_ptr <int> ptr_cp{move(ptr)};
    cout << *ptr_cp << endl;

    ptr_cp.reset();
    if( !ptr_cp )
        cout << "nullptr" << endl;
    else
        cout << *ptr_cp << endl;

    return 0;
}

 

요렇게 하면 nullptr이라는 문자열이 출력된다

ptr_cp에는 ptr의 메모리 영역을 move하여 갖고 있는데,

ptr_cp에 대해서 reset()을 하여 메모리 영역을 삭제했기 때문이다

 


 

내일은 shared_ptr 공부할래