icon안동민 개발노트

스마트 포인터


스마트 포인터의 개념

 스마트 포인터는 C++ 11에서 도입된 기능으로, 동적으로 할당된 메모리를 자동으로 관리해주는 객체입니다.

 기존의 생 포인터(raw pointer)가 가진 여러 문제점들을 해결하기 위해 설계되었습니다.

 스마트 포인터의 주요 장점

  1. 메모리 누수 방지
  2. 댕글링 포인터(dangling pointer) 문제 해결
  3. 예외 발생 시에도 안전한 리소스 해제
  4. 소유권 개념의 명확한 표현

 스마트 포인터는 <memory> 헤더에 정의되어 있습니다.

 스마트 포인터에는 다음과 같은 종류가 있습니다.

  • std::unique_ptr
  • std::shared_ptr
  • std::weak_ptr

std::unique_ptr

 std::unique_ptr은 특정 객체에 대한 유일한 소유권을 가지는 스마트 포인터입니다.

 주요 특징

  • 복사할 수 없음 (이동은 가능)
  • 포인터가 범위를 벗어나면 자동으로 메모리 해제
  • 배열 형태의 메모리도 관리 가능
  • 커스텀 삭제자(deleter) 지정 가능
예제
#include <memory>
#include <iostream>
 
class MyClass {
public:
    MyClass() { std::cout << "MyClass 생성\n"; }
    ~MyClass() { std::cout << "MyClass 소멸\n"; }
    void doSomething() { std::cout << "MyClass 작업 수행\n"; }
};
 
int main() {
    std::unique_ptr<MyClass> ptr = std::make_unique<MyClass>();
    ptr->doSomething();
 
    // 소유권 이전
    std::unique_ptr<MyClass> ptr2 = std::move(ptr);
    
    if (ptr == nullptr) {
        std::cout << "ptr은 더 이상 객체를 소유하지 않습니다.\n";
    }
 
    return 0;
}

std::shared_ptr

 std::shared_ptr은 여러 포인터가 하나의 객체를 공유할 수 있게 해주는 스마트 포인터입니다.

 주요 특징

  • 참조 횟수(reference count)를 통해 객체의 수명 관리
  • 마지막 shared_ptr이 소멸될 때 객체 삭제
  • 순환 참조 문제 발생 가능 (weak_ptr로 해결)
예제
#include <memory>
#include <iostream>
 
class MyClass {
public:
    MyClass(int v) : value(v) { std::cout << "MyClass(" << value << ") 생성\n"; }
    ~MyClass() { std::cout << "MyClass(" << value << ") 소멸\n"; }
    int getValue() const { return value; }
private:
    int value;
};
 
int main() {
    std::shared_ptr<MyClass> ptr1 = std::make_shared<MyClass>(10);
    std::cout << "참조 횟수: " << ptr1.use_count() << std::endl;
 
    {
        std::shared_ptr<MyClass> ptr2 = ptr1;
        std::cout << "참조 횟수: " << ptr1.use_count() << std::endl;
        std::cout << "값: " << ptr2->getValue() << std::endl;
    }
 
    std::cout << "참조 횟수: " << ptr1.use_count() << std::endl;
 
    return 0;
}

std::weak_ptr

 std::weak_ptr은 std::shared_ptr이 관리하는 객체에 대한 약한 참조를 제공합니다.

 주요 특징

  • 참조 횟수를 증가시키지 않음
  • 순환 참조 문제 해결에 사용
  • 객체의 존재 여부 확인 가능
예제
#include <memory>
#include <iostream>
 
class MyClass {
public:
    MyClass(int v) : value(v) { std::cout << "MyClass(" << value << ") 생성\n"; }
    ~MyClass() { std::cout << "MyClass(" << value << ") 소멸\n"; }
    int getValue() const { return value; }
private:
    int value;
};
 
int main() {
    std::weak_ptr<MyClass> weakPtr;
    
    {
        auto sharedPtr = std::make_shared<MyClass>(20);
        weakPtr = sharedPtr;
        
        if (auto temp = weakPtr.lock()) {
            std::cout << "객체 값: " << temp->getValue() << std::endl;
        } else {
            std::cout << "객체가 더 이상 존재하지 않습니다.\n";
        }
    }
    
    if (auto temp = weakPtr.lock()) {
        std::cout << "객체 값: " << temp->getValue() << std::endl;
    } else {
        std::cout << "객체가 더 이상 존재하지 않습니다.\n";
    }
 
    return 0;
}

스마트 포인터의 사용 지침

  1. 가능한 한 생 포인터 대신 스마트 포인터를 사용하세요.
  2. 객체에 대한 단독 소유권이 필요할 때는 std::unique_ptr을 사용하세요.
  3. 여러 곳에서 객체를 공유해야 할 때만 std::shared_ptr을 사용하세요.
  4. 순환 참조를 피하기 위해 std::weak_ptr을 사용하세요.
  5. 스마트 포인터 생성 시 std::make_unique와 std::make_shared를 사용하세요.

실습

  1. 다음 코드를 스마트 포인터를 사용하여 메모리 누수를 방지하도록 수정하세요.
class Resource {
public:
    Resource() { std::cout << "Resource 생성\n"; }
    ~Resource() { std::cout << "Resource 소멸\n"; }
    void use() { std::cout << "Resource 사용\n"; }
};
 
void processResource() {
    Resource* res = new Resource();
    res->use();
    // 실수로 delete를 호출하지 않음
}
 
int main() {
    processResource();
    return 0;
}
  1. std::shared_ptr을 사용하여 간단한 그래프 구조를 구현하세요. 노드 클래스는 다른 노드에 대한 포인터를 여러 개 가질 수 있어야 합니다.
  2. 위의 그래프 구조에서 발생할 수 있는 순환 참조 문제를 std::weak_ptr을 사용하여 해결하세요.

 참고자료

  • Effective Modern C++ by Scott Meyers
  • C++ Core Guidelines
  • cppreference.com - Smart pointers
  • "Back to Basics : Smart Pointers" - CppCon 2019 presentation by Rainer Grimm