C++

[C++] 스마트 포인터

hanseongbugi 2024. 12. 18. 16:38

unique_ptr

  • C++에서 메모리를 잘못된 방식으로 관리하였을 때 크게 두 가지 종류의 문제가 발생
    1. 메모리 누수, 메모리를 사용하고 해제 하지 않음 - RALL 패턴을 사용하면 해결 가능
    2. 해제한 메모리를 다시 참조 
Data* data = new Data();
Date* data2 = data;

// data 의 입장 : 사용 다 했으니 소멸시켜야지.
delete data;

// ...

// data2 의 입장 : 나도 사용 다 했으니 소멸시켜야지
delete data2;
  • 위 문제가 발생한 이유는 만들어진 객체의 소유권이 명확하지 않았기 때문
    • 포인터에 객체의 유일한 소유권을 부여하면 해결 가능
    • 이 포인터 외에 객체를 소멸시킬 수 없다고 하면 double free는 발생하지 않음
#include <iostream>
#include <memory>

class A {
    int *data;
public:
    A() 
    {
        std::cout << "자원을 획득함!" << std::endl;
        data = new int[100];
     }
     void some() 
     { 
         std::cout << "일반 포인터와 동일하게 사용가능!" << std::endl; 
     }
     ~A() 
     {
         std::cout << "자원을 해제함!" << std::endl;
         delete[] data;
     }
};

void doSomething() {
    std::unique_ptr<A> pa(new A());
    pa->some();
}

int main() 
{ 
    doSomething(); 
}
output
자원을 획득함!
일반 포인터와 동일하게 사용가능!
자원을 해제함!
  • unique_ptr을 정의하기 위해서는 템플릿에 인자로, 포인터가 가리킬 클래스를 전달하면 된다.
std::unique_ptr<A> pa(new A());
  • unique_ptr을 통해 RALL 패턴을 사용할 수 있다.
    • pa는 스택에 정의된 객체이기 때문에 doSomethig() 함수가 종료될 때 자동으로 소멸자가 호출
    • unique_ptr은 소멸자 안에서 자신이 가리키는 자원을 해제

unique_ptr 소유권 이전

  • unique_ptr은 복사가 되지 않지만, 소유권은 이전할 수 있다.
#include <iostream>
#include <memory>

class A {
    int *data;
public:
    A() 
    {
        std::cout << "자원을 획득함!" << std::endl;
        data = new int[100];
     }
     void some() 
     { 
         std::cout << "일반 포인터와 동일하게 사용가능!" << std::endl; 
     }
     ~A() 
     {
         std::cout << "자원을 해제함!" << std::endl;
         delete[] data;
     }
};

void doSomething() {
    std::unique_ptr<A> pa(new A());
    std::cout << "pa: ";
    pa->some();

    std::unique_ptr<A> pb = std::move(pa);
    std::cout << "pb: ";
    pb->some();
}

int main() 
{ 
    doSomething(); 
}
output
자원을 획득함!
pa : 일반 포인터와 동일하게 사용가능!
pb : 일반 포인터와 동일하게 사용가능!
자원을 해제함!

unique_ptr 쉽게 생성하기

  • C++14부터 unique_ptr을 간단하게 생성할 수 있는 std::make_unique 함수를 제공
#include <iostream>
#include <memory>

class Foo {
    int a, b;
public:
    Foo(int a, int b) : a(a), b(b) 
    { 
        std::cout << "생성자 호출!" << std::endl; 
    }
    void print() 
    { 
        std::cout << "a : " << a << ", b : " << b << std::endl; 
    }
    ~Foo() 
    { 
        std::cout << "소멸자 호출!" << std::endl; 
    }
};

int main() 
{
    auto ptr = std::make_unique<Foo>(3, 5);
    ptr->print();
}
output
생성자 호출!
a : 3, b : 5
소멸자 호출!

shared_ptr

  • 유일하게 객체를 소유하는 unique_ptr과 다르게, shared_ptr로 가리킬 경우
    • 다른 shared_ptr 역사 그 객체를 가리킬 수 있다.
std::shared_ptr<A> p1(new A());
std::shared_ptr<A> p2(p1);  // p2 역시 생성된 객체 A 를 가리킨다.

// 반면에 unique_ptr 의 경우
std::unique_ptr<A> p1(new A());
std::unique_ptr<A> p2(p1);  // 컴파일 오류!
  • 참조 개수(reference count)를 통해 참조 개수가 0이 되면 가리키고 있는 객체를 해제한다.
  • shared_ptr은 control block을 통해 shared_ptr들이 필요한 정보를 공유하는 방식으로 구현되었다
    • 복사 생성될 때마다 제어 블록의 위치만 공유
  • shared_ptr을 사용할 때 주소 값을 넘겨주면 안된다.

make_shared로 생성

std::shared_ptr<A> p1 = std::make_shared<A>();
  • 생성자의 인자들을 받아 객체와 shared_ptr의 제어 블록까지 한 번에 동적할당 한 후 shared_ptr을 반환
  • 생성자의 인자가 존재할 경우 make_shared에 인자로 전달해 주면 된다.

weak_ptr

  • 일반 포인터와 shared_ptr 사이에 위치한 스마트 포인터
  • 스마트 포인터 처럼 객체를 안전하게 참조할 수 있지만, shared_ptr과는 다르게 참조 개수를 늘리진 않음
  • 어떤 객체를 weak_ptr이 가리키고 있다고 하더라도, 다른 shared_ptr들이 가리키고 있지 않으면 메모리에서 소멸되었을 것
    • 따라서 weak_ptr 자체로는 원래 객체를 참조할 수 없고, 반드시 shared_ptr로 변환해서 사용
    • 이때 가리키고 있는 객체가 이미 소멸되었다면 빈 shared_ptr으로 변환
    • 아닐경우 해당 객체를 가리키는 shared_ptr으로 변환된다.

 

 

 

 

 

출처

https://modoocode.com/229

 

씹어먹는 C ++ - <13 - 1. 객체의 유일한 소유권 - unique_ptr>

모두의 코드 씹어먹는 C ++ - <13 - 1. 객체의 유일한 소유권 - unique_ptr> 작성일 : 2018-09-18 이 글은 58539 번 읽혔습니다. 이번 강좌에서는 C++ 의 RAII 패턴unique_ptr안녕하세요 여러분! 지난번 강좌에서 다

modoocode.com

https://modoocode.com/252

 

씹어먹는 C ++ - <13 - 2. 자원을 공유할 때 - shared_ptr 와 weak_ptr>

모두의 코드 씹어먹는 C ++ - <13 - 2. 자원을 공유할 때 - shared_ptr 와 weak_ptr> 작성일 : 2018-12-21 이 글은 48942 번 읽혔습니다. 이번 강좌에서는 shared_ptrenable_shared_from_thisweak_ptr에 대해 다룹니다.안녕하

modoocode.com