스마트 포인터( Smart Pointer )란?

 

  • C++은 Unmanaged Language로서 메모리 할당과 해제를 프로그래머가 직접 관리해주어야 합니다. 하지만, 협업을 하다보면 프로그래머의 실수로 메모리의 누수가 발생될 수 있습니다. 그래서 C++은 이러한 누수를 예방하고자 스마트 포인터를 사용할 수 있습니다.




스마트 포인터 동작원리

 

  • 스마트 포인터는 RAII( Resource Acquisition Is Initialization ) 디자인 패턴을 기반으로 설계되어 있습니다. 초기화된 스마트 포인터 객체가 스코프를 벗어나면 소멸자가 호출되는데 이 때 소멸자 내부에서는 해당 자원에 대해서 반환할지 여부를 결정하고 delete를 호출합니다.




스마트 포인터의 종류와 동작 방식

 

unique_ptr

 

int main()
{
    std::unique_ptr<CChild> p1 = std::make_unique<CChild>();

    std::unique_ptr<CChild> p2(new CChild);

    // 소유권 이전
    auto p3 = std::move(p2);

    // 대입 연산자를 통한 소유권 이전은 불가능합니다.
    //auto p4 = p3;

        return 1;
}

 

  • unique_ptr은 동적 할당 받은 메모리에 대한 참조를 하나의 스마트 포인터에서만 관리하기 위해 사용하는 스마트 포인터입니다. 그렇기 때문에 소유권을 복사할 수 없으며 오로지 std::move를 통해서만 소유권 이전이 가능합니다.




shared_ptr

 

int main()
{
    std::shared_ptr<CChild> p1(new CChild);

    std::shared_ptr<CChild> p2 = std::make_shared<CChild>();

    // 복사 생성자
    std::shared_ptr<CChild> p3(p2);

    std::shared_ptr<CChild> p4;

    // 대입 연산자
    p4 = p3;

    std::cout << p4.use_count();

    return 1;
}

 

  • shared_ptr은 동적 할당 받은 메모리에 대한 참조를 여러 스마트 포인터에서 관리하기 위해 사용하는 스마트 포인터입니다. 내부적으로 여러 스마트 포인터에서 관리하기 위해 해당 메모리에 대한 참조를 몇개의 스마트 포인터가 참조하고 있는지 관리합니다. shared_ptr이 스코프를 벗어나서 소멸자가 호출될 때 마다 참조하는 메모리에 대한 참조 카운트를 감소시키며 참조 카운트를 0으로 만든 shared_ptr이 delete를 호출하여 메모리를 반환합니다.




weak_ptr

 

// 순환 참조 발생 코드
#include <iostream>
#include <Windows.h>

class CTest {
public:

    CTest() {
        std::cout << "생성자\n";
    }

    ~CTest() {
        std::cout << "소멸자\n";
    }

    void ShowMe() {
        std::cout << "Hello\n";
    }

    std::shared_ptr<CTest> p;
};

int main()
{
    auto s1 = std::make_shared<CTest>();
    auto s2 = std::make_shared<CTest>();

    s1->p = s2;
    s2->p = s1;

    return 1;
}




// weak_ptr을 사용하여 순환 참조 방지
#include <iostream>
#include <Windows.h>

class CTest {
public:

    CTest() {
        std::cout << "생성자\n";
    }

    ~CTest() {
        std::cout << "소멸자\n";
    }

    void ShowMe() {
        std::cout << "Hello\n";
    }

    std::weak_ptr<CTest> p;
};

int main()
{
    auto s1 = std::make_shared<CTest>();
    auto s2 = std::make_shared<CTest>();

    s1->p = s2;
    s2->p = s1;

    auto s = s1->p.lock();
    s->ShowMe();

    return 1;
}

 

  • weak_ptr은 shared_ptr리 소유하는 메모리에 대한 접근 방법을 제공하지만, 참조 카운트 계산은 하지 않는 스마트 포인터입니다. 만약, shared_ptr로 생성된 2개의 인스턴스가 서로를 가리키는 shared_ptr을 멤버 변수로 가지고 있다면은 해당 인스턴스들은 절대로 메모리가 반환되지 않습니다. 이러한 현상을 순환 참조( Circular Reference )라고 하며 이러한 현상을 방지하고자 weak_ptr을 사용합니다.

 

  • weak_ptr은 참조하는 메모리에 접근할 때 lock 멤버 함수를 이용해서 shared_ptr 생성하여 접근할 수 있습니다.




auto_ptr을 사용하지 않는 이유

 

#include <iostream>
#include <Windows.h>

class CTest {
public:

    CTest() {
        std::cout << "생성자\n";
    }

    ~CTest() {
        std::cout << "소멸자\n";
    }

    void ShowMe() {
        std::cout << "ASDF";
    }
};

int main()
{
    // 배열 스마트 포인터를 지원하지 않음
    //std::auto_ptr<CTest> p(new CTest[20]);

    std::auto_ptr<CTest> p1(new CTest);
    std::auto_ptr<CTest> p2(p1);

    std::cout << p1.get() << std::endl;
    std::cout << p2.get() << std::endl;

    return 1;
}

 

  • auto_ptr은 사용이 권장되지 않는 스마트 포인터입니다. 그 이유는 두 가지가 있습니다. 첫 번째로는 배열 형식으로 스마트 포인터를 생성할 수 없습니다. 두 번째로는 얕은 복사를 수행하기 때문에 얕은 복사 이후 기존 스마트 포인터가 참조하는 값은 nullptr로 초기화되는 문제가 있기 때문에 사용이 권장되지 않습니다.




'Programming Language > C, C++' 카테고리의 다른 글

C++ 람다(lambda)란?  (0) 2023.03.25
const와 constexpr(constant expression)의 차이점  (0) 2023.02.27
C++ 가상함수와 추상 클래스  (0) 2023.01.27
new와 malloc의 차이점  (0) 2023.01.27
얕은 복사와 깊은 복사  (0) 2023.01.27

+ Recent posts