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

2025. 3. 28. 10:04Programming Languages/C++

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

스마트 포인터는 동적으로 할당된 메모리 관리를 자동화하는 클래스 템플릿입니다.

9.3.1 unique_ptr

unique_ptr은 해당 자원의 유일한 소유자를 나타냅니다:

#include <iostream>
#include <memory>
#include <string>

class Resource {
private:
    std::string name;
    
public:
    Resource(const std::string& n) : name(n) {
        std::cout << "Resource " << name << " 생성됨" << std::endl;
    }
    
    ~Resource() {
        std::cout << "Resource " << name << " 소멸됨" << std::endl;
    }
    
    void use() const {
        std::cout << "Resource " << name << " 사용 중" << std::endl;
    }
};

// unique_ptr을 함수의 반환 값으로 사용
std::unique_ptr<Resource> createResource(const std::string& name) {
    return std::make_unique<Resource>(name);  // C++14
    // C++11에서는: return std::unique_ptr<Resource>(new Resource(name));
}

// unique_ptr을 함수 매개변수로 전달 (이동)
void consumeResource(std::unique_ptr<Resource> ptr) {
    ptr->use();
    // 함수가 종료되면 Resource가 자동으로 소멸됨
}

int main() {
    // unique_ptr 생성
    std::unique_ptr<Resource> res1 = std::make_unique<Resource>("First");
    
    // 멤버 접근
    res1->use();
    
    // 소유권 이전
    std::unique_ptr<Resource> res2 = std::move(res1);
    
    // res1은 이제 nullptr
    if (!res1) {
        std::cout << "res1은 이제 비어 있습니다." << std::endl;
    }
    
    // res2 사용
    res2->use();
    
    // 함수 반환 값으로 unique_ptr 받기
    auto res3 = createResource("Third");
    res3->use();
    
    // 함수에 unique_ptr 전달 (소유권 이전)
    consumeResource(std::move(res3));
    
    // res3은 이제 nullptr
    if (!res3) {
        std::cout << "res3은 이제 비어 있습니다." << std::endl;
    }
    
    // 배열 관리
    std::unique_ptr<int[]> numbers = std::make_unique<int[]>(5);
    for (int i = 0; i < 5; i++) {
        numbers[i] = i * 10;
    }
    
    std::cout << "숫자: ";
    for (int i = 0; i < 5; i++) {
        std::cout << numbers[i] << " ";
    }
    std::cout << std::endl;
    
    // 블록이 끝나면 모든 자원이 자동으로 소멸됨
    
    return 0;
}

9.3.2 shared_ptr

shared_ptr은 참조 카운팅을 통해 여러 소유자가 리소스를 공유할 수 있게 합니다:

#include <iostream>
#include <memory>
#include <vector>
#include <string>

class Resource {
private:
    std::string name;
    
public:
    Resource(const std::string& n) : name(n) {
        std::cout << "Resource " << name << " 생성됨" << std::endl;
    }
    
    ~Resource() {
        std::cout << "Resource " << name << " 소멸됨" << std::endl;
    }
    
    void use() const {
        std::cout << "Resource " << name << " 사용 중" << std::endl;
    }
    
    std::string getName() const {
        return name;
    }
};

// shared_ptr 컨테이너 반환 함수
std::vector<std::shared_ptr<Resource>> createResources() {
    std::vector<std::shared_ptr<Resource>> resources;
    
    resources.push_back(std::make_shared<Resource>("Vector Resource 1"));
    resources.push_back(std::make_shared<Resource>("Vector Resource 2"));
    resources.push_back(std::make_shared<Resource>("Vector Resource 3"));
    
    return resources;  // 벡터가 리소스의 소유권을 유지함
}

// 기존 shared_ptr 사용 함수
void useResource(std::shared_ptr<Resource> res) {
    std::cout << "참조 카운트: " << res.use_count() << std::endl;
    res->use();
}

int main() {
    // shared_ptr 기본 사용
    auto res1 = std::make_shared<Resource>("Shared");
    
    {
        auto res2 = res1;  // 참조 카운트 증가
        std::cout << "블록 내부 - 참조 카운트: " << res1.use_count() << std::endl;
        
        // 두 포인터 모두 같은 객체를 가리킴
        res1->use();
        res2->use();
    }  // 블록이 끝나면 res2가 소멸되고 참조 카운트 감소
    
    std::cout << "블록 이후 - 참조 카운트: " << res1.use_count() << std::endl;
    
    // 함수에 shared_ptr 전달 (참조 카운트 증가)
    useResource(res1);
    
    // shared_ptr 컨테이너
    auto resources = createResources();
    
    std::cout << "컨테이너 내 리소스:" << std::endl;
    for (const auto& res : resources) {
        std::cout << "- " << res->getName() 
                  << " (참조 카운트: " << res.use_count() << ")" << std::endl;
    }
    
    // 컨테이너에서 요소 추출
    auto extracted = resources[1];
    resources.erase(resources.begin() + 1);
    
    std::cout << "추출 후 참조 카운트: " << extracted.use_count() << std::endl;
    
    // shared_ptr 재설정
    res1.reset();  // res1이 가리키던 자원 해제 (참조 카운트가 0이 되면 소멸)
    std::cout << "res1 리셋 후: " << (res1 ? "not null" : "null") << std::endl;
    
    // shared_ptr로 배열 관리 (C++17 이전에는 커스텀 삭제자 필요)
    auto numbers = std::shared_ptr<int[]>(new int[5], std::default_delete<int[]>());
    // C++17 이후: auto numbers = std::make_shared<int[]>(5);
    
    for (int i = 0; i < 5; i++) {
        numbers[i] = i * 100;
    }
    
    std::cout << "숫자: ";
    for (int i = 0; i < 5; i++) {
        std::cout << numbers[i] << " ";
    }
    std::cout << std::endl;
    
    // 모든 블록이 끝나면 참조 카운트가 0이 되어 모든 자원이 소멸됨
    
    return 0;
}

9.3.3 weak_ptr

weak_ptr은 shared_ptr이 관리하는 객체에 대한 약한 참조를 제공합니다. 순환 참조 문제를 해결하는 데 유용합니다:

#include <iostream>
#include <memory>
#include <string>

// 상호 참조 문제를 보여주는 클래스
class Person {
private:
    std::string name;
    // shared_ptr 대신 weak_ptr 사용
    std::weak_ptr<Person> friend_;  // 순환 참조 방지
    
public:
    Person(const std::string& n) : name(n) {
        std::cout << "Person " << name << " 생성됨" << std::endl;
    }
    
    ~Person() {
        std::cout << "Person " << name << " 소멸됨" << std::endl;
    }
    
    void setFriend(const std::shared_ptr<Person>& f) {
        friend_ = f;  // weak_ptr는 참조 카운트를 증가시키지 않음
    }
    
    void showFriend() const {
        // weak_ptr을 사용하려면 lock()으로 shared_ptr 얻기
        if (auto sharedFriend = friend_.lock()) {
            std::cout << name << "의 친구: " << sharedFriend->name << std::endl;
        } else {
            std::cout << name << "의 친구는 없습니다." << std::endl;
        }
    }
    
    std::string getName() const {
        return name;
    }
};

int main() {
    // weak_ptr 기본 사용
    std::weak_ptr<int> weakPtr;
    
    {
        auto sharedPtr = std::make_shared<int>(42);
        weakPtr = sharedPtr;  // shared_ptr을 weak_ptr에 할당
        
        std::cout << "shared_ptr이 유효할 때:" << std::endl;
        std::cout << "weakPtr.expired(): " << std::boolalpha << weakPtr.expired() << std::endl;
        
        if (auto shared = weakPtr.lock()) {
            std::cout << "값: " << *shared << std::endl;
        } else {
            std::cout << "weak_ptr이 유효하지 않습니다." << std::endl;
        }
    }  // 블록이 끝나면 sharedPtr이 소멸되어 참조 카운트가 0이 됨
    
    std::cout << "\nshared_ptr이 소멸된 후:" << std::endl;
    std::cout << "weakPtr.expired(): " << weakPtr.expired() << std::endl;
    
    if (auto shared = weakPtr.lock()) {
        std::cout << "값: " << *shared << std::endl;
    } else {
        std::cout << "weak_ptr이 유효하지 않습니다." << std::endl;
    }
    
    // 순환 참조 문제 해결
    std::cout << "\n순환 참조 예제:" << std::endl;
    
    auto alice = std::make_shared<Person>("Alice");
    auto bob = std::make_shared<Person>("Bob");
    
    std::cout << "초기 참조 카운트 - Alice: " << alice.use_count() 
              << ", Bob: " << bob.use_count() << std::endl;
    
    // 친구 관계 설정
    alice->setFriend(bob);
    bob->setFriend(alice);
    
    std::cout << "친구 설정 후 참조 카운트 - Alice: " << alice.use_count() 
              << ", Bob: " << bob.use_count() << std::endl;
    
    alice->showFriend();
    bob->showFriend();
    
    // alice와 bob이 서로를 weak_ptr로 참조하고 있으므로
    // 여기서 shared_ptr이 소멸되면 객체도 소멸됨
    std::cout << "\n스마트 포인터가 소멸됩니다..." << std::endl;
    
    return 0;
}

'Programming Languages > C++' 카테고리의 다른 글

람다 표현식과 함수형 프로그래밍  (0) 2025.03.28
다중 스레딩과 동시성(C++11)  (0) 2025.03.28
파일 입출력  (0) 2025.03.28
고급 주제  (0) 2025.03.28
챕터8. 실습 문제  (0) 2025.03.28