-
C++ 스마트 포인터(Smart Pointer) 이해하기c++ 2025. 3. 2. 21:34728x90
C++의 스마트 포인터는 메모리 관리 자동화를 위한 도구로, 수동으로 new 및 delete를 호출하는 번거로움을 줄이고 메모리 누수(memory leak) 문제를 방지하는 데 도움을 준다. 스마트 포인터는 C++11 이후부터 표준 라이브러리에 포함되었으며, std::unique_ptr, std::shared_ptr, std::weak_ptr가 주요 타입이다.
이번 글에서는 스마트 포인터의 종류와 사용법을 예제와 함께 자세히 살펴본다.
1. std::unique_ptr - 단일 소유권 스마트 포인터
특징
- 객체에 대한 단 하나의 소유자만 존재할 수 있다.
- 복사가 불가능하지만 이동(move) 가능하다.
- 객체가 소유권을 잃으면 자동으로 해제된다.
사용 예제
#include <iostream> #include <memory> class Resource { public: Resource() { std::cout << "Resource 생성" << std::endl; } ~Resource() { std::cout << "Resource 소멸" << std::endl; } }; int main() { std::unique_ptr<Resource> ptr1 = std::make_unique<Resource>(); // std::unique_ptr<Resource> ptr2 = ptr1; // 오류: 복사 불가 std::unique_ptr<Resource> ptr2 = std::move(ptr1); // 이동 가능 if (!ptr1) { std::cout << "ptr1은 더 이상 리소스를 소유하지 않음" << std::endl; } }
주요 메서드
메서드 설명
std::make_unique<T>(args...) unique_ptr 객체 생성 (C++14 이상) std::move(ptr) 소유권 이전(이동) release() 소유권 해제 후 원시 포인터 반환 reset() 새로운 객체를 가리키도록 변경
2. std::shared_ptr - 참조 카운트 기반 스마트 포인터
특징
- 여러 개의 shared_ptr가 하나의 객체를 공유할 수 있다.
- 참조 카운트(reference count)를 유지하여 마지막 shared_ptr가 소멸될 때만 객체가 해제된다.
- 복사 및 이동이 가능하다.
사용 예제
#include <iostream> #include <memory> class Resource { public: Resource() { std::cout << "Resource 생성" << std::endl; } ~Resource() { std::cout << "Resource 소멸" << std::endl; } }; void useResource(std::shared_ptr<Resource> ptr) { std::cout << "useResource 내부의 참조 카운트: " << ptr.use_count() << std::endl; } int main() { std::shared_ptr<Resource> ptr1 = std::make_shared<Resource>(); std::cout << "초기 참조 카운트: " << ptr1.use_count() << std::endl; std::shared_ptr<Resource> ptr2 = ptr1; // 공유됨 std::cout << "ptr2 생성 후 참조 카운트: " << ptr1.use_count() << std::endl; useResource(ptr1); ptr2.reset(); std::cout << "ptr2 해제 후 참조 카운트: " << ptr1.use_count() << std::endl; }
주요 메서드
메서드 설명
std::make_shared<T>(args...) shared_ptr 객체 생성 use_count() 현재 참조 카운트 반환 reset() 소유권 해제
3. std::weak_ptr - 순환 참조 방지용 스마트 포인터
특징
- shared_ptr와 함께 사용되며 참조 카운트를 증가시키지 않는다.
- shared_ptr가 관리하는 객체가 해제되었는지 확인할 수 있다.
- lock()을 사용하여 shared_ptr로 변환 후 객체를 안전하게 사용한다.
사용 예제
#include <iostream> #include <memory> class Resource { public: Resource() { std::cout << "Resource 생성" << std::endl; } ~Resource() { std::cout << "Resource 소멸" << std::endl; } }; int main() { std::shared_ptr<Resource> sharedPtr = std::make_shared<Resource>(); std::weak_ptr<Resource> weakPtr = sharedPtr; // 참조 카운트 증가 없음 if (auto locked = weakPtr.lock()) { std::cout << "객체가 아직 존재함" << std::endl; } sharedPtr.reset(); // 객체 해제 if (weakPtr.expired()) { std::cout << "객체가 삭제됨" << std::endl; } }
주요 메서드
메서드 설명
lock() shared_ptr로 변환 (객체가 존재하면) expired() 객체가 해제되었는지 확인
4. 스마트 포인터 사용 시 주의할 점
- 순환 참조 방지
- shared_ptr를 사용할 때 객체 간 상호 참조(예: A가 B를 shared_ptr로 가리키고, B도 A를 shared_ptr로 가리키는 경우)가 발생하면 참조 카운트가 감소하지 않아 메모리 누수가 발생할 수 있다.
- 이를 방지하려면 한쪽은 weak_ptr를 사용해야 한다.
class B; class A { public: std::shared_ptr<B> b; }; class B { public: std::weak_ptr<A> a; // 순환 참조 방지 };
- unique_ptr을 복사하지 않도록 주의
- unique_ptr는 복사할 수 없고, 이동만 가능하므로 함수 인자로 받을 때는 std::move()를 사용해야 한다.
void process(std::unique_ptr<Resource> ptr) { // ptr은 이동된 후 원래 소유자는 더 이상 접근할 수 없음 }
- 스마트 포인터를 불필요하게 사용하지 않기
- 스마트 포인터는 동적 할당된 객체를 관리할 때만 필요하다. 스택 할당된 객체는 스마트 포인터로 감쌀 필요가 없다.
마무리
스마트 포인터를 사용하면 C++의 동적 메모리 관리를 보다 안전하고 효율적으로 할 수 있다. unique_ptr, shared_ptr, weak_ptr 각각의 특징과 사용법을 이해하고, 적절한 상황에서 활용하는 것이 중요하다.
728x90'c++' 카테고리의 다른 글
고급 문법과 최적화 (0) 2025.03.02 코드 품질과 유지보수성 (0) 2025.03.02 C++ 구조체 vs 클래스: 차이점과 올바른 활용법 (0) 2025.02.24 Modern C++ (0) 2025.02.06 C++ 네트워크 프로그래밍과 소켓 통신 (0) 2025.02.04