IMCAT.DEV
로그인 가입
C++ 중급 자료구조

C++ 스마트 포인터 (Smart Pointers)

C++ 스마트 포인터 (Smart Pointers) — 중급

스마트 포인터 (Smart Pointers)

1. 도입 (왜 배워야 하는가)

스마트 포인터는 C++에서 메모리 관리를 보다 안전하고 효율적으로 할 수 있게 도와주는 도구입니다. 특히, 객체의 생명 주기를 자동으로 관리하여 메모리 누수(memory leak)와 같은 문제를 방지합니다. 실무에서는 메모리 관리가 매우 중요합니다. 메모리 누수가 발생하면 시스템 성능이 저하되거나, 프로그램이 비정상적으로 종료될 수 있습니다. 따라서 스마트 포인터를 사용하지 않으면 객체의 메모리를 해제하지 못해 메모리 누수가 발생하거나, 이미 해제된 메모리를 접근하는 오류가 발생할 수 있습니다.

2. 핵심 개념 설명

스마트 포인터는 C++에서 관리되는 포인터로, 주로 다음과 같은 세 가지 유형이 있습니다:

  1. std::unique_ptr
  2. - 한 객체에 대해 단 하나의 포인터만 가질 수 있습니다. 소유권이 이전될 수 있지만, 복사할 수는 없습니다. - 비유: 독점적인 소유권. 한 사람이 한 물건을 독점적으로 소유하는 것과 같습니다.

  3. std::shared_ptr
  4. - 여러 포인터가 같은 객체를 공유할 수 있습니다. 참조 카운트를 사용하여 객체의 생명 주기를 관리합니다. - 비유: 공동 소유. 여러 사람이 함께 소유하는 물건처럼, 누가 사용하더라도 소유권이 유지됩니다.

  5. std::weak_ptr
  6. - std::shared_ptr와 함께 사용되며, 소유권을 가지지 않는 포인터입니다. 주로 순환 참조를 방지하는 데 사용됩니다. - 비유: 친구의 물건을 잠깐 빌리는 것. 친구가 물건을 계속 가지고 있는 한 사용하지만, 소유권은 없습니다.

    스마트 포인터 유형소유권복사 가능 여부
    std::unique_ptrYesNo
    std::shared_ptrYesYes
    std::weak_ptrNoNo

    3. 기본 코드 예제

    다음은 std::unique_ptr의 기본 사용법을 보여주는 코드입니다.

    #include <iostream>
    #include <memory>
    
    int main() {
        std::unique_ptr<int> ptr(new int(10)); // int 타입의 스마트 포인터 생성
        std::cout << "값: " << ptr << std::endl; // 스마트 포인터가 가리키는 값 출력
    
        // ptr의 소유권을 다른 unique_ptr로 이전
        std::unique_ptr<int> ptr2 = std::move(ptr);
        std::cout << "ptr2의 값: " << ptr2 << std::endl; // ptr2가 소유권을 받음
    
        // ptr을 더 이상 사용할 수 없음
        // std::cout << *ptr; // 오류 발생: ptr은 nullptr임
    
        return 0;
    }

    실행 결과

    값: 10
    ptr2의 값: 10

    코드 한 줄씩 동작 설명

  7. std::unique_ptr<int> ptr(new int(10));: 정수형 10을 가리키는 unique_ptr 생성.
  8. std::cout << "값: " << *ptr << std::endl;: 스마트 포인터가 가리키는 값을 출력.
  9. std::unique_ptr<int> ptr2 = std::move(ptr);: ptr의 소유권을 ptr2로 이전.
  10. std::cout << "ptr2의 값: " << *ptr2 << std::endl;: ptr2가 이제 소유한 값 출력.
  11. 주의: ptr은 nullptr가 되어 더 이상 사용할 수 없음.
  12. 4. 실전 활용 예제

    스마트 포인터는 다양한 실무 상황에서 활용됩니다.

    예제 1: 쇼핑몰 장바구니

    장바구니에 담긴 상품을 관리할 때, 각 상품 객체를 shared_ptr로 관리하면 여러 사용자가 장바구니를 공유할 수 있습니다.

    #include <iostream>
    #include <memory>
    #include <vector>
    
    class Product {
    public:
        Product(std::string name) : name(name) {}
        std::string name;
    };
    
    int main() {
        std::vector<std::shared_ptr<Product>> cart;
        cart.push_back(std::make_shared<Product>("상품1"));
        cart.push_back(std::make_shared<Product>("상품2"));
    
        for (const auto& product : cart) {
            std::cout << "장바구니 상품: " << product->name << std::endl;
        }
    
        return 0;
    }

    예제 2: 순환 참조 방지

    weak_ptr를 사용하여 순환 참조 문제를 해결할 수 있습니다.
    #include <iostream>
    #include <memory>
    
    class A;
    
    class B {
    public:
        std::weak_ptr<A> aPtr; // A의 weak_ptr
    };
    
    class A {
    public:
        std::shared_ptr<B> bPtr; // B의 shared_ptr
    };
    
    int main() {
        std::shared_ptr<A> a = std::make_shared<A>();
        std::shared_ptr<B> b = std::make_shared<B>();
        a->bPtr = b; // A가 B를 소유
        b->aPtr = a; // B가 A를 weak하게 참조
    
        return 0;
    }

    5. 흔한 실수와 주의사항

    1. 소유권 이전을 잊음

    • 잘못된 코드
    •   std::unique_ptr<int> p1(new int(5));
        std::unique_ptr<int> p2 = p1; // 오류: 복사할 수 없음
    • 올바른 코드
    •   std::unique_ptr<int> p1(new int(5));
        std::unique_ptr<int> p2 = std::move(p1); // 소유권 이전

      2. shared_ptr의 순환 참조

    • 잘못된 코드
    •   class A { std::shared_ptr<B> b; };
        class B { std::shared_ptr<A> a; }; // 순환 참조 발생
    • 올바른 코드
    •   class A { std::shared_ptr<B> b; };
        class B { std::weak_ptr<A> a; }; // weak_ptr로 수정

      6. 정리

    • 스마트 포인터는 메모리 관리를 자동으로 해주어 안전성을 높입니다.
    • std::unique_ptr, std::shared_ptr, std::weak_ptr의 세 가지 유형이 있습니다.
    • 소유권 이전과 순환 참조 문제를 주의해야 합니다.
    • 다음에 학습하면 좋은 주제는 "C++의 RAII(자원 획득은 초기화)"입니다.