C++ 초급 - 5. 포인터와 참조 (Pointers and References) (4 - 스마트 포인터 (std::unique_ptr, std::shared_ptr, std::weak_ptr))

2025. 2. 12. 18:56프로그래밍 언어/C++

📌 5.4 스마트 포인터 (std::unique_ptr, std::shared_ptr, std::weak_ptr)

C++11부터 스마트 포인터(Smart Pointer)가 도입되어,
개발자가 직접 delete를 호출하지 않아도 메모리가 자동 관리된다.

스마트 포인터를 사용하면 메모리 누수를 방지하고, 포인터의 안전성을 향상시킬 수 있다.
C++ 표준 라이브러리 <memory> 헤더를 포함하면 사용할 수 있다.


📌 1. 스마트 포인터의 필요성

일반 포인터(new/delete)를 사용하면 메모리 누수(Memory Leak)가 발생할 위험이 있다.
특히, 예외(Exception)가 발생하는 경우 delete가 호출되지 않아 누수가 발생할 수 있다.

💡 일반 포인터의 문제점 (메모리 누수 발생)

#include <iostream>

void memoryLeak() {
    int* ptr = new int(100);
    std::cout << *ptr << std::endl;
    // delete ptr;  // ❌ 메모리 해제하지 않으면 누수 발생!
}

int main() {
    memoryLeak();  // 여러 번 호출하면 메모리 누적됨
    return 0;
}

🔹 위 코드는 delete ptr;이 없으면 메모리 누수가 발생할 수 있다.

💡 스마트 포인터(std::unique_ptr)를 사용하면 자동으로 메모리 해제 가능

#include <iostream>
#include <memory>

void safeMemory() {
    std::unique_ptr<int> ptr = std::make_unique<int>(100);  
    std::cout << *ptr << std::endl;
}

int main() {
    safeMemory();  // ✅ 메모리 자동 해제됨
    return 0;
}

🔹 스마트 포인터를 사용하면 delete를 명시적으로 호출하지 않아도 된다.


📌 2. std::unique_ptr (단독 소유권을 가지는 스마트 포인터)

std::unique_ptr는 특정 객체에 대한 소유권을 단독으로 가지는 스마트 포인터이다.
즉, 같은 객체를 여러 unique_ptr이 가리킬 수 없으며,
소유권 이전을 위해 std::move()를 사용해야 한다.

💡 기본 문법

std::unique_ptr<데이터타입> 포인터변수 = std::make_unique<데이터타입>(초기값);

💡 예제: std::unique_ptr 사용법

#include <iostream>
#include <memory>  // 스마트 포인터 사용을 위한 헤더

int main() {
    // 스마트 포인터 생성
    std::unique_ptr<int> ptr = std::make_unique<int>(42);
    std::cout << "값: " << *ptr << std::endl;

    // 소유권 이전 (std::move 사용)
    std::unique_ptr<int> ptr2 = std::move(ptr);
    std::cout << "ptr2가 가리키는 값: " << *ptr2 << std::endl;

    // ptr은 더 이상 유효하지 않음
    if (!ptr) {
        std::cout << "ptr은 nullptr입니다." << std::endl;
    }

    return 0;
}

🔹 출력 결과

값: 42
ptr2가 가리키는 값: 42
ptr은 nullptr입니다.

💡 설명

  • std::unique_ptr<int> ptr = std::make_unique<int>(42); → ptr이 42를 소유.
  • std::unique_ptr<int> ptr2 = std::move(ptr); → ptr2가 소유권을 가져감.
  • ptr은 nullptr이 되어 더 이상 사용할 수 없음.

주의: std::unique_ptr는 복사(copy)가 불가능하고, 이동(move)만 가능하다.

std::unique_ptr<int> ptr2 = ptr;  // ❌ 복사 불가능 (컴파일 오류)

📌 3. std::shared_ptr (참조 카운트를 사용하는 공유 스마트 포인터)

std::shared_ptr는 여러 개의 포인터가 같은 객체를 공유할 수 있도록 지원한다.
내부적으로 참조 카운트(Reference Count)를 유지하여
마지막 shared_ptr이 소멸될 때 자동으로 메모리를 해제한다.

💡 기본 문법

std::shared_ptr<데이터타입> 포인터변수 = std::make_shared<데이터타입>(초기값);

💡 예제: std::shared_ptr 사용법

#include <iostream>
#include <memory>  // 스마트 포인터 사용을 위한 헤더

int main() {
    std::shared_ptr<int> ptr1 = std::make_shared<int>(100);
    std::cout << "ptr1 참조 카운트: " << ptr1.use_count() << std::endl;

    // 새로운 shared_ptr가 같은 객체를 참조
    std::shared_ptr<int> ptr2 = ptr1;
    std::cout << "ptr2 추가 후 참조 카운트: " << ptr1.use_count() << std::endl;

    // ptr1을 제거
    ptr1.reset();
    std::cout << "ptr1 해제 후 ptr2 참조 카운트: " << ptr2.use_count() << std::endl;

    return 0;
}

🔹 출력 결과

ptr1 참조 카운트: 1
ptr2 추가 후 참조 카운트: 2
ptr1 해제 후 ptr2 참조 카운트: 1

💡 설명

  • std::make_shared<int>(100); → ptr1이 100을 소유.
  • ptr2 = ptr1; → ptr2가 같은 객체를 참조 (참조 카운트 증가).
  • ptr1.reset(); → ptr1이 해제되어 참조 카운트 감소.

📌 4. std::weak_ptr (순환 참조 문제 해결)

std::shared_ptr는 서로가 서로를 참조할 경우 순환 참조(Circular Reference) 문제가 발생할 수 있다.
이를 해결하기 위해 약한 참조(Weak Reference)를 제공하는 std::weak_ptr를 사용한다.

💡 예제: std::weak_ptr을 사용한 순환 참조 해결

#include <iostream>
#include <memory>

class Node {
public:
    int value;
    std::shared_ptr<Node> next;
    
    Node(int val) : value(val) {
        std::cout << "Node 생성: " << value << std::endl;
    }

    ~Node() {
        std::cout << "Node 삭제: " << value << std::endl;
    }
};

int main() {
    std::shared_ptr<Node> node1 = std::make_shared<Node>(10);
    std::shared_ptr<Node> node2 = std::make_shared<Node>(20);

    // ❌ 순환 참조 발생 (메모리 누수 가능)
    // node1->next = node2;
    // node2->next = node1;

    // ✅ `std::weak_ptr`을 사용하여 순환 참조 방지
    node1->next = node2;
    node2->next = std::weak_ptr<Node>(node1);

    return 0;
}

🔹 출력 결과

Node 생성: 10
Node 생성: 20
Node 삭제: 20
Node 삭제: 10

💡 설명

  • std::weak_ptr은 참조 카운트를 증가시키지 않아 순환 참조를 방지할 수 있다.

📌 5. 정리

스마트 포인터 특징  사용 목적
std::unique_ptr 단독 소유 객체를 하나의 포인터만 관리
std::shared_ptr 참조 카운트 증가 여러 개의 포인터가 공유 가능
std::weak_ptr 순환 참조 방지 shared_ptr과 함께 사용하여 순환 참조 해결