[C/C++] 스마트 포인터(Smart Pointer)

할랑말랑·2026년 3월 11일

C/C++

목록 보기
17/45

스마트 포인터(Smart Pointer)

C++에서 메모리 관리를 보다 효율적이고 안전하게 하기 위해 제공되는 클래스 템플릿이다. 일반적인 포인터와 달리, 스마트 포인터는 메모리의 소유권과 생명주기를 관리하여 메모리 누수와 같은 문제를 예방한다.

스마트 포인터의 종류

1. std::unique_ptr

소유권이 유일한 포인터이다. 하나의 unique_ptr만이 특정 객체를 소유할수 있다. 복사가 불가능하고, 이동(std::move)만 가능하다.

기본 사용 예시

#include <iostream>
#include <memory>

using namespace std;

int main()
{
    // unique_ptr 생성
    unique_ptr<int> ptr1 = make_unique<int>(20);

    // 소유권 이동 (move 사용)
    unique_ptr<int> ptr2 = move(ptr1);

    if (!ptr1)
    {
        cout << "ptr1은 이제 비어 있습니다." << endl;
    }
    cout << "ptr2의 값: " << *ptr2 << endl;

    return 0;
}

일반 클래스에서 사용 예시

#include <iostream>
#include <memory>

using namespace std;

class MyClass
{
public:
    MyClass(int val) : value(val)
    {
        cout << "MyClass 생성: " << value << endl;
    }
    ~MyClass()
    {
        cout << "MyClass 소멸: " << value << endl;
    }
    void display() const
    {
        cout << "값: " << value << endl;
    }
private:
    int value;
};

int main()
{
    // unique_ptr로 MyClass 객체 관리
    unique_ptr<MyClass> myObject = make_unique<MyClass>(42);
    // MyClass 멤버 함수 호출
    myObject->display();

    // 소유권 이동
    unique_ptr<MyClass> newOwner = move(myObject);

    if (!myObject)
    {
        cout << "myObject는 이제 비어 있습니다." << endl;
    }

    newOwner->display();

    // 범위를 벗어나면 newOwner가 관리하는 메모리 자동 해제
    return 0;
}

2. std::shared_ptr

여러 포인터가 동일한 객체를 공유할 수 있다. 참조 카운트를 사용하여 객체가 더 이상 필요하지 않을 때 메모리를 해제한다.

기본 사용 예시

#include <iostream>
#include <memory> // shared_ptr 사용

using namespace std;

int main()
{
    // shared_ptr 생성
    shared_ptr<int> ptr1 = make_shared<int>(10);

    // ptr1의 참조 카운트 출력
    cout << "ptr1의 참조 카운트: " << ptr1.use_count() << endl; // 출력: 1

    // ptr2가 ptr1과 리소스를 공유
    shared_ptr<int> ptr2 = ptr1;
    cout << "ptr2 생성 후 참조 카운트: " << ptr1.use_count() << endl; // 출력: 2

    // ptr2가 범위를 벗어나면 참조 카운트 감소
    ptr2.reset();
    cout << "ptr2 해제 후 참조 카운트: " << ptr1.use_count() << endl; // 출력: 1

    // 범위를 벗어나면 ptr1도 자동 해제
    return 0;
}

일반 클래스에서 사용 예시

#include <iostream>
#include <memory>

using namespace std;

class MyClass
{
public:
    MyClass(int val) : value(val)
    {
        cout << "MyClass 생성: " << value << endl; // 출력: MyClass 생성: 42
    }
    ~MyClass()
    {
        cout << "MyClass 소멸: " << value << endl; // 출력: MyClass 소멸: 42
    }
    void display() const
    {
        cout << "값: " << value << endl; // 출력: 값: 42
    }
private:
    int value;
};

int main()
{
    // shared_ptr로 MyClass 객체 관리
    shared_ptr<MyClass> obj1 = make_shared<MyClass>(42);

    // 참조 공유
    shared_ptr<MyClass> obj2 = obj1;

    cout << "obj1과 obj2의 참조 카운트: " << obj1.use_count() << endl; // 출력: 2

    obj2->display(); // 출력: 값: 42

    // obj2를 해제해도 obj1이 객체를 유지
    obj2.reset();

    cout << "obj2 해제 후 obj1의 참조 카운트: " << obj1.use_count() << endl; // 출력: 1

    return 0;
}

순환 참조

#include <iostream>
#include <memory>

class B; // Forward declaration

class A
{
public:
    std::shared_ptr<B> b_ptr;
    ~A() { std::cout << "A destroyed\n"; }
};

class B
{
public:
    std::shared_ptr<A> a_ptr;
    ~B() { std::cout << "B destroyed\n"; }
};

int main()
{
    auto a = std::make_shared<A>();
    auto b = std::make_shared<B>();

    a->b_ptr = b;
    b->a_ptr = a;

    // main 함수가 끝나도 A와 B는 서로 참조 중이라 메모리 해제가 안 됨
    return 0;
}

main 함수가 끝나도 a,b는 서로 참조중이라 메모리 해제가 되지 않는다.

3. std::weak_ptr

shared_ptr과 함께 사용되며, 참조 카운트를 증가시키지 않는다. 따라서 순환 참조를 방지할 수 있다.
shared_ptr가 소멸된 후에도 객체에 접근할 수 있는 방법을 제공한다.

lock함수로 유효성을 확인하고 사용

#include <iostream>
#include <memory>

class A
{
public:
    void say_hello()
    {
        std::cout << "Hello from A\n";
    }
};

class B
{
public:
    std::weak_ptr<A> a_ptr;

    void useA()
    {
        if (auto a_shared = a_ptr.lock()) // 유효성 확인
        {
            a_shared->say_hello();
        }
        else
        {
            std::cout << "A is no longer available.\n";
        }
    }
};

int main()
{
    std::shared_ptr<B> b = std::make_shared<B>();

    {
        std::shared_ptr<A> a = std::make_shared<A>();
        b->a_ptr = a;
        b->useA(); // A가 유효하므로 Hello 출력
    } // A는 scope를 벗어나며 소멸됨

    b->useA(); // A는 이미 소멸되었기 때문에 메시지 출력
}

C++에서 std::weak_ptr 객체를 통해 사용되는 함수로, 약한 포인터가 참조하고 있는 shared_ptr 객체가 여전히 유효한지 확인하고, 유효하면 해당 객체에 대한 shared_ptr을 반환한다.

순환 참조 해결

#include <iostream>
#include <memory>

class B; // Forward declaration

class A
{
public:
    std::shared_ptr<B> b_ptr;
    ~A() { std::cout << "A destroyed\n"; }
};

class B
{
public:
    std::weak_ptr<A> a_ptr; // weak_ptr로 변경
    ~B() { std::cout << "B destroyed\n"; }
};

int main()
{
    auto a = std::make_shared<A>();
    auto b = std::make_shared<B>();

    a->b_ptr = b;
    b->a_ptr = a; // weak_ptr로 참조

    return 0;
}

스마트 포인트 사용시 main함수에서 shared_ptr로 선언된 객체가 스코프를 벗어날 때, 해당 shared_ptr가 먼저 소멸되고 shared_ptr이 소유하는 객체의 참조포인트가 감소하고, 참조 카운트가 0이되면 해당 객체의 소멸자가 호출된다.

스마트 포인터의장점

  • 자동 메모리 관리 : 스마트 포인터는 객체가 더 이상 필요하지 않을 때 자동으로 메모리를 해제하므로 메모리 누수를 방지한다
  • 안전성 향상 : 포인터의 소유권과 생명 주기를 명확히 관리하여 오류를 줄인다.
  • RAII 패턴 : 자원 획득 시 초기화 원칙을 따르며, 객체의 생명 주기를 관리한다.

주의사항

  • 참조 카운트 오버헤드 : shared_ptr는 참조 카운트를 유지하기 때문에 성능 오버헤드가 발생할 수 있다.
  • 순환 참조 : shared_ptr를 사용한 순환 참조가 발생할 수 있으므로, 이러한 경우에는 weak_ptr를 사용하여 문제를 해결한다.

0개의 댓글