ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Effective Modern C++] Smart pointer : std::unique_ptr<>
    C++/Effective Modern C++ 2022. 5. 9. 17:10

    스마트 포인터란 Raw pointer를 감싸는 래퍼(Wrapper) 클래스 입니다.

    기존의 포인터가 가진 몇 가지 문제점들을 회피하면서, 포인터의 기능을 제공합니다.

     

    이번 글에서는 스마트 포인터의 한 종류인 std::unique_ptr<>에 대하여 살펴보도록 하겠습니다.

     


     

    1. std::unique_ptr<>

     

    더보기

    std::unique_ptr은 스마트 포인터를 사용할 때 가장 우선적으로 고려되는 포인터 입니다.

    기본적으로 대부분의 연산에서 Raw pointer와 같은 명령을 수행합니다.

    이것은 메모리 및 CPU의 사용에서 Raw pointer에 비해 무겁지 않음을 의미하고, Raw pointer를 사용할 때의 비용과 거의 동일하다는 뜻이 됩니다.

     

    선언 및 사용은 아래와 같이 할 수 있습니다.

    #include <iostream>
    #include <memory>
    
    int main
        int a = 10;
        int* b = &a;
        std::unique_ptr<int> c(new int(a));
    
        std::cout << *b << "\n";
        std::cout << *c << "\n";
    }
    10
    10

     

    이후 문단들에서 std::unique_ptr의 특징에 대하여 살펴보도록 하겠습니다.

     

    2. 독점적 소유권

     

    더보기

    std::unique_ptr은 포인팅 하는 대상에 대한 독점적 소유권(Exclusive ownership)을 가지고 있습니다.

    NULL이 아닌 std::unique_ptr은 항상 자신이 가리키는 객체를 소유하고 있으며, 이 소유권은 다른 포인터와 공유되지 않습니다.

    독점적인 소유권을 가져야 하기 때문에, std::unique_ptr은 복사 연산을 허용하지 않는 이동 전용 형식 입니다.

    #include <iostream>
    #include <memory>
    
    int main(){
        std::unique_ptr<Integer> a(new Integer(10));
        std::unique_ptr<Integer> b = a;
        std::unique_ptr<Integer> c(a);
    }
    C2280 : 'std::unique_ptr<>(&)': 삭제된 함수를 참조하려고 합니다.

    위와 같이 복사 배정 연산자, 혹은 복사 생성자를 통해 std::unique_ptr을 생성하려 할 경우 컴파일이 실패합니다.

     

    3. Delete, custom deleter

     

    더보기

    std::unique_ptr은 소멸 시 자신이 가리키는 자원을 파괴합니다.

    이동 연산의 경우, 이동 후 기존 std::unique_ptr은 nullptr로 설정됩니다.

    std::unique_ptr의 파괴 작업은 내부적으로 가리키고 있는 Raw pointer에 delete연산을 적용하는 방식으로 수행됩니다.

    이 때, delete연산 이외에 특정 작업이 필요할 수 있습니다.

    이러한 경우를 위해 std::unique_ptr은 커스텀 삭제자를 사용할 수 있도록 할 수 있습니다.

    #include <iostream>
    #include <memory>
    
    class Integer {
    public:
        Integer(int val) : data(val) { }
        virtual ~Integer();
    private:
        int data;
    };
    
    int main() {
        auto del_int = [](Integer* p_int) {
            std::cout << "Delete Integer\n";
            delete p_int;
        };
    
        std::unique_ptr<Integer, decltype(del_int)> a(new Integer(10), del_int);
    }
    Delete Integer

    커스텀 삭제자를 사용할 경우, std::unique_ptr에 그 형식과 해당 함수를 지정해야 합니다.

    지정된 함수는 해당 객체가 소멸되는 시점에 호출됩니다.

     

    4. std::unique_ptr<T[]>

     

    더보기

    std::unique_ptr<>의 형태는 두 가지 입니다.

    첫 번째는 개별 객체를 위한 std::unique_ptr<T>이고, 두 번째는 배열을 위한 std::unique_ptr<T[]>입니다.

    지금은 비 권장 기능인 초기형태의 스마트 포인터인 std::auto_ptr<>은 배열의 해제에 관련된 이슈가 존재했는데, std::unique_ptr은 객체의 형태를 단일 객체와 배열로 세분화하면서 이러한 문제를 해결했습니다.

     

    두 객체는 단일 객체인지, 배열인지의 차이가 존재하기 때문에 일부 특정 연산이 제한되어 있습니다.

    예를 들어 단일 객체의 std::unique_ptr<T>은 operator[]를 제공하지 않으며, 배열 객체인 std::unique_ptr<T[]>은 역참조 연산 (operator*및 operator->를 제공하지 않습니다.

     

    하지만 배열 형식으로 포인터를 관리하는 데 std::unique_ptr<T[]>는 권장되지 않습니다.

    기본적으로 사용되는 템플릿이 내장 배열이라는 점에서, std::vector<>혹은 std::array<>등과 같은 STL의 배열이 나은 경우가 많기 때문입니다.

     


     

    std::unique_ptr은 다른 스마트 포인터인 std::shared_ptr로의 유연한 변환이 가능합니다.

    std::shared_ptr은 이름 그대로 공유가 가능한 스마트 포인터로, std::unique_ptr과는 비슷하지만 여러 부분에서 다른 특징을 가지고 있습니다.

    이에 관해서는 다음 글에서 살펴보도록 하겠습니다.

     

    감사합니다.

    댓글

Designed by Tistory.