삶의 공유

C++ 스마트 포인터(Smart Pointer) 완전 정리 본문

Programing언어/C++

C++ 스마트 포인터(Smart Pointer) 완전 정리

dkrehd 2025. 9. 29. 22:40
728x90
반응형

📘 C++ 스마트 포인터(Smart Pointer) 완전 정리

 

현대 C++에서 **스마트 포인터(Smart Pointer)**는 자원 관리(Resource Management)를 단순화하고, 안전하게 만드는 핵심 기능입니다. 이 글에서는 스마트 포인터의 원리부터, 직접 구현 예제, 그리고 표준 라이브러리에서 제공하는 스마트 포인터까지 정리해보겠습니다.


1. 스마트 포인터란?

포인터처럼 동작하는 객체

스마트 포인터는 일반 포인터의 역할을 하면서도 객체이기 때문에

  • 생성자
  • 소멸자
  • 복사/대입/이동 연산자
    등의 과정을 통해 다양한 추가 작업을 자동으로 수행할 수 있습니다.

특히 소멸자에서 자원 해제를 보장하기 때문에,
new 로 생성한 객체를 직접 delete 하지 않아도 됩니다.


2. 스마트 포인터의 원리

핵심은 바로 연산자 오버로딩입니다.

template <typename T>
class Ptr {
    T* obj{};
public:
    explicit Ptr(T* p = nullptr) : obj{p} {}
    ~Ptr() { delete obj; }   // 자동 delete

    T* operator->() { return obj; }
    T& operator*()  { return *obj; }
};
 
 
  • operator->() : 내부 포인터 반환 → p->go() 같은 문법 지원
  • operator*() : 내부 객체 참조 반환 → (*p).go() 같은 문법 지원

즉,

 
Ptr<Car> p(new Car);
p->go();       // (p.operator->())->go();
(*p).go();     // (p.operator*()).go();
 

이렇게 포인터처럼 자연스럽게 사용할 수 있습니다.


3. 왜 Raw Pointer 대신 Smart Pointer?

 
Car* p = new Car;
// ... 사용 ...
delete p;  // 직접 해제해야 함
  • delete 를 깜빡하면 메모리 누수 발생
  • 예외나 조기 return 으로 delete를 놓치면 문제 심각
  • 여러 포인터가 같은 객체를 가리키면 이중 delete 위험
 
Ptr<Car> p(new Car);
// 스코프 종료 시 ~Ptr() 자동 호출 → delete obj
 

👉 RAII(Resource Acquisition Is Initialization) 원칙에 따라 객체 생명주기에 맞춰 자원 해제가 자동으로 이루어집니다.


4. 주의사항

  1. operator*() 는 반드시 참조(T&) 를 반환해야 합니다.
    • 값으로 반환하면 임시 객체가 만들어져 원본과 달라지는 문제가 생길 수 있습니다.
  2. operator->() 는 보통 원시 포인터(T)* 를 반환해야 합니다.
    • 그렇지 않으면 체이닝에서 문제가 발생할 수 있습니다.
  3. nullptr 상태일 때 *p 또는 p-> 호출 → UB(정의되지 않은 동작)
    • 필요하다면 assert 등을 걸어 디버깅 보조.

5. 표준 스마트 포인터

C++ 표준은 <memory> 헤더에서 3가지 스마트 포인터를 제공합니다.

5.1 std::unique_ptr<T>

  • 단독 소유 (unique ownership)
  • 복사 불가, 이동만 가능
  • 가장 가볍고 안전한 스마트 포인터
 
auto p = std::make_unique<Car>();
p->go();
 

5.2 std::shared_ptr<T>

  • 여러 곳에서 소유 (shared ownership)
  • 참조 카운트(reference count) 로 관리
  • 마지막 하나가 파괴될 때 자원 해제
 
std::shared_ptr<Car> p1 = std::make_shared<Car>();
std::shared_ptr<Car> p2 = p1;  // 참조 카운트 증가
 

5.3 std::weak_ptr<T>

  • shared_ptr의 보조 포인터 (비소유)
  • 참조 카운트에는 영향을 주지 않음
  • lock() 으로 shared_ptr 변환 후 사용 가능
  • 순환 참조(cyclic reference) 문제 방지

6. Shallow Copy 문제와 스마트 포인터

  • 클래스에 포인터 멤버가 있다면, 디폴트 복사 생성자는 단순히 주소만 복사(얕은 복사) 합니다.
  • 그 결과 두 객체가 같은 메모리를 가리켜 이중 delete 문제가 생깁니다.
  • 해결책:
    1. 깊은 복사(Deep Copy) – 메모리 새로 할당 후 내용 복사
    2. 참조 카운팅(Reference Counting) – shared_ptr
    3. 소유권 이전(Move Semantics) – unique_ptr
    4. 복사 금지(Delete) – 자원 유일 소유만 허용

👉 스마트 포인터는 이런 문제를 자동으로 해결해 주는 도구입니다.


7. 정리

  • 스마트 포인터 = 포인터 역할을 하는 객체
  • 핵심 원리 = ->, * 연산자 오버로딩 + 소멸자에서 자원 해제
  • 장점
    1. 자원 관리 자동화 → 메모리 누수 방지
    2. 예외 안전성 보장
    3. 소유권 모델(unique, shared, weak) 명확
  • 실무에서는 직접 구현보다 표준 스마트 포인터 사용이 권장됩니다.

📌 한 줄 요약

스마트 포인터는 “포인터처럼 보이지만 자원 관리까지 책임지는 객체”이다.
RAII 원칙을 지키며 안전하고 현대적인 C++ 코드를 작성할 수 있게 도와준다.

반응형