포스트

C++ 스마트 포인터

C++ 스마트 포인터

C++ 스마트 포인터 정리

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

스마트 포인터는 C++에서 메모리를 자동으로 관리하는 포인터입니다. 일반적인 new/delete 기반의 메모리 관리를 대체하며, 메모리 누수 방지 및 안정적인 메모리 해제 기능을 제공합니다.

C++98에서는 std::auto_ptr이 도입되었지만, 설계상의 문제로 인해 C++11 이후 std::unique_ptr, std::shared_ptr, std::weak_ptr로 대체되었습니다.


✅ 2. 주요 스마트 포인터 종류

2.1 std::unique_ptr (단독 소유, 복사 불가)

  • 단 하나의 스마트 포인터만 특정 객체를 소유할 수 있음.
  • 복사 불가능, 대신 std::move()를 사용하여 소유권 이동 가능.

🔹 사용 예제

1
2
3
4
5
6
7
8
9
10
#include <iostream>
#include <memory>

int main() {
    std::unique_ptr<int> ptr = std::make_unique<int>(10);
    std::cout << *ptr << std::endl;  // 출력: 10
    
    std::unique_ptr<int> ptr2 = std::move(ptr);  // 소유권 이동
    std::cout << (ptr ? "ptr is valid" : "ptr is null") << std::endl;
}

📌 특징

  • std::move()를 사용하여 소유권 이동 가능.
  • 범위를 벗어나면 자동으로 메모리 해제.
  • 배열(T[])도 관리 가능std::unique_ptr<int[]> arr(new int[10]);

2.2 std::shared_ptr (공유 소유, 참조 카운트)

  • 여러 개의 포인터가 같은 객체를 공유 가능.
  • 참조 카운트(use_count()) 를 사용하여 마지막 shared_ptr가 소멸될 때 객체 자동 삭제.

🔹 사용 예제

1
2
3
4
5
6
7
8
9
#include <iostream>
#include <memory>

int main() {
    std::shared_ptr<int> ptr1 = std::make_shared<int>(10);
    std::shared_ptr<int> ptr2 = ptr1;  // 참조 카운트 증가
    
    std::cout << *ptr1 << " ref count: " << ptr1.use_count() << std::endl;
}

📌 특징

  • 여러 포인터가 하나의 객체를 공유 가능.
  • 객체가 더 이상 사용되지 않을 때(use_count() == 0) 자동으로 삭제.
  • std::make_shared<T>()를 사용하면 메모리 할당 비용 감소.

std::shared_ptr 개요 및 주의점

std::shared_ptr는 C++11에서 도입된 스마트 포인터로, 참조 카운트를 통해 메모리 관리를 자동화합니다. 그러나 모든 상황에서 이상적이지 않으며, 단점과 내부 구조를 이해하고 적절히 사용하는 것이 중요합니다.

1. 장점

  • 참조 카운트를 기반으로 한 자동 메모리 관리
  • 복수 객체에서 동일 리소스를 공유 가능
  • 메모리 누수를 줄이는 데 효과적

2. 단점 및 주의사항

2.1 순환 참조 문제 (Circular Reference)

  • 두 객체가 서로를 shared_ptr로 참조하면 참조 카운트가 0이 되지 않아 메모리 누수 발생

예시

1
2
3
4
5
6
7
8
9
10
struct A;
struct B;

struct A {
    std::shared_ptr<B> b;
};

struct B {
    std::shared_ptr<A> a;
};
  • 해결: 한쪽은 std::weak_ptr로 변경

2.2 제어 블록 오버헤드

  • 객체 외에 제어 블록(control block)이 추가로 메모리에 존재
  • 제어 블록에는 참조 카운트, deleter 등이 포함됨 → 메모리 사용량 증가

2.3 성능 오버헤드

  • 참조 카운트 연산은 thread-safe → 내부적으로 원자 연산 사용
  • 멀티스레드 환경에서는 shared_ptr의 생성/복사/소멸이 성능 저하 유발 가능

2.4 소유권이 불분명

  • 여러 곳에서 객체를 소유할 수 있어, 객체의 생명 주기 예측이 어려움
  • 리소스 생명주기를 명확하게 제어해야 한다면 unique_ptr이 적합함

2.5 잘못된 사용: shared_ptr(p.get())

1
2
std::shared_ptr<Foo> a(new Foo);
std::shared_ptr<Foo> b(a.get()); // 위험: 서로 다른 제어 블록!
  • ab는 서로 다른 제어 블록을 가짐 → double delete 위험

2.3 std::weak_ptr (순환 참조 방지, 참조 카운트 증가 없음)

  • shared_ptr참조 카운트를 증가시키지 않고 객체를 참조할 수 있음.
  • 객체가 존재하는지(expired()) 확인 후 사용 가능.

🔹 사용 예제

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <iostream>
#include <memory>

int main() {
    std::weak_ptr<int> wptr;
    {
        std::shared_ptr<int> sptr = std::make_shared<int>(100);
        wptr = sptr;  // 참조 카운트 증가 안함
    } // sptr이 범위를 벗어나면 객체 소멸

    if (auto sp = wptr.lock()) {
        std::cout << *sp << std::endl;
    } else {
        std::cout << "객체가 소멸됨" << std::endl;
    }
}

📌 특징

  • shared_ptr과 함께 사용하여 순환 참조(Circular Reference) 방지.
  • lock()을 사용하여 유효한 shared_ptr를 얻을 수 있음.

✅ 3. std::auto_ptr (C++17에서 제거됨)

std::auto_ptrC++98에서 도입된 스마트 포인터지만, 복사 시 소유권이 강제로 이동되는 문제로 인해 C++11에서 사용이 비추천되고, C++17에서 완전히 제거됨.

🔹 std::auto_ptr 사용 예제 (비추천)

1
2
3
4
5
6
7
8
9
#include <iostream>
#include <memory>

int main() {
    std::auto_ptr<int> ptr1(new int(10));
    std::auto_ptr<int> ptr2 = ptr1;  // ptr1의 소유권이 ptr2로 이동
    
    std::cout << (ptr1.get() == nullptr ? "ptr1 is null" : "ptr1 is valid") << std::endl;
}

📌 std::auto_ptr 대신 std::unique_ptr 또는 std::shared_ptr를 사용할 것.


✅ 4. 부가적인 스마트 포인터

4.1 boost::scoped_ptr (단순한 unique_ptr)

  • Boost 라이브러리에서 제공하는 스마트 포인터.
  • unique_ptr과 유사하지만, 소유권 이동(std::move())이 불가능.

4.2 boost::intrusive_ptr (사용자 정의 참조 카운트)

  • shared_ptr과 유사하지만, 참조 카운트를 사용자가 직접 관리.

4.3 boost::shared_array (배열 관리 스마트 포인터)

  • shared_ptr의 배열 버전.
  • C++11 이후에는 std::unique_ptr<T[]>로 대체 가능.

✅ 5. 스마트 포인터 비교

스마트 포인터소유권참조 카운트주요 특징
std::unique_ptr단독 소유❌ 없음복사 불가, 이동(std::move()) 가능
std::shared_ptr공유 소유✅ 있음참조 카운트 증가/감소
std::weak_ptr공유 소유 (X)❌ 없음shared_ptr의 참조 카운트 증가 없이 참조
std::auto_ptr단독 소유 (비추천)❌ 없음C++17에서 제거됨

✅ 6. 결론

스마트 포인터는 new/delete 없이 자동 메모리 관리를 지원.
C++11 이후 std::unique_ptr, std::shared_ptr, std::weak_ptr을 사용해야 함.
std::auto_ptr은 C++17에서 제거되었으며, std::unique_ptr로 대체.
std::shared_ptr을 사용할 때는 순환 참조를 방지하기 위해 std::weak_ptr을 활용.

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.