스마트 포인터

C++ 메모리 관리 포인터

Posted by Start Bootstrap on December 20, 2019

스마트 포인터(Smart Pointer)

스마트 포인터(Smart Pointer)는 C++에서 동적으로 메모리를 관리해주는 기능입니다. 동적으로 메모리를 관리한다는 뜻은 코드를 작성하는 프로그래머가 정확한 시점에 Delete를 하지 않아도 메모리를 해제 한다는 것입니다. 프로그래머가 정확한 시점에 메모리를 해제하는 것은 매우 어렵고 실패할 경우 문제가 발생할 수 있습니다. 같은 메모리를 여러번 해제하거나, 혹은 예기치 못한 상황으로 메모리를 해제 하지 못하거나 이러한 상황에서 메모리를 사용해서 다른 메모리의 손상을 가져 온다던가 하는 상황이 있을 수 있습니다.

스마트 포인터는 크게 두 가지 방식으로 메모리를 관리합니다. 첫 번째로 메모리를 단독 소유권을 가지는 것입니다. 단독 소유권을 가진다는 것은 여러 객체나 변수, 코드 등에서 가지지 않는 것입니다. 오직 처음 들어간 장소만 유효합니다. 그래서 스코프를 벗어나거나 해서 더 이상 참조할 수 없거나 없어지거나 하면 참조하던 메모리를 해제합니다.

두 번째는 여러 변수 및 객체에서 서로 다른 이름으로 같은 메모리를 참조하는 것입니다. 이것을 메모리 앨리어싱(Memory Aliasing)이라고 합니다. 이러한 경우 메모리를 해제 하는 것에 대한 규칙이 있어야 합니다. 예를 들면 어떤 경우에 마지막으로 메모리가 해제가 되서 더 이상 접근할 수 없음을 알게 되는 것인지에 대해서겠죠. 그래서 메모리의 참조 횟수(Reference Counting)을 계산해서 해제할 타이밍을 확인하는 방식이 있습니다. 전자는 std::unique_ptr이고 후자는 std::shared_ptr이 있습니다.

unique_ptr
        
                #include 

                int main( void )
                {
                    NormalClass* normalClass = new NormalClass();
                    if ( nullptr == normalClass )
                    {
                        return 0;
                    }

                    normalClass->open();
                    normalClass->doAction();
                    normalClass->close();

                    delete normalClass;
                    normalClass = nullptr;

                    return 0;
                }
        
    

위는 메모리를 할당하고 객체에 관해서 처리를 마치고 정확하게 할당하는 코드를 작성했습니다.

        
            #include 

                int main( void )
                {
                    auto normalClass = make_unique(); // C++14의 make_unique
                    // c++14보다 아래라면 unique_ptr(new T(std::forward(args)...)) 과 같은 형식으로 사용
                    // 여기서는 unique_ptr normalClass(new NormalClass()); 
                    if ( nullptr == normalClass )
                    {
                        return 0;
                    }

                    normalClass->open();
                    normalClass->doAction();
                    normalClass->close();
                    
                    // 메모리 해제를 별도로 할 필요가 없다...

                    return 0;
                }
        
    

그 외에도 release() 등을 통해서 소유권을 해제할수도 있습니다. reset()등으로 소유권자를 바꿀 수도 있습니다. release()를 사용하면 기존의 방식처럼 직접 해제 해야 합니다.

shared_ptr
        
                #include 

                int main( void )
                {
                    auto normalClass = make_shared();
                    if ( nullptr == normalClass )
                    {
                        return 0;
                    }

                    normalClass->open();
                    normalClass->doAction();
                    normalClass->close();

                    return 0;
                }
        
    

차이점은 release()는 사용할 수 없습니다. 그리고 Reference Counting을 통해서 인스턴스의 수를 파악합니다.

weak_ptr

때로는 Reference Counting을 증가 시키지 않고 혹은 메모리를 직접 소유 하지 않고 뭔가 리소스를 얻고 싶은 경우도 있습니다. 이럴때 weak_ptr을 사용할 수 있습니다. 파이썬에 익숙한 분들이라면 weakref를 떠올릴 수 있습니다. CPython에서는 모든 메모리가 Reference Counting 방식으로 관리가 되기 때문에 weakref를 떠올리면 거의 비슷하지 않을까 생각합니다.