본문 바로가기
프로그래밍 언어/C++

C++ 11 / 스마트 포인터 shared_ptr, weak_ptr

by Nighthom 2023. 2. 15.

1. 선언 방법

std::shared_ptr<A> p1(new A());
std::shared_ptr<A> p2(p1);
// 문법 오류 X

shared_ptr은 기본적으로 해당 객체가 몇개 선언되었는지에 대한 count를 제공한다. 해당 count가 0이 되면 해당 객체를 가리키는 포인터가 전부 파괴되었다고 생각하고 해당 포인터를 메모리에서 해제한다. 

std::cout << p1.use_count();  // 2
std::cout << p2.use_count();  // 2

shared_ptr 또한 unique_ptr처럼 make_shared 함수를 제공한다.

auto p1 = std::make_shared<A>();

2. std::enable_shared_from_this

A* a = new A();

std::shared_ptr<A> pa1(a);
std::shared_ptr<A> pa2(a);

std::cout << pa1.use_count() << std::endl;   // 1
std::cout << pa2.use_count() << std::endl;   // 1

shared_ptr에 만약 어떠한 객체의 포인터 값을 전달한다면 각 shared_ptr의 참조 계수가 공유되지 않는다(각각 서로가 1이라는 참조계수를 갖게 된다). 따라서 shared_ptr을 생성할 때 이런 식으로 포인터 값을 직접적으로 전달하는 행위는 지양해야 한다. 

 

하지만 어쩔 수 없이 shared_ptr에 포인터값을 직접 전달해야 하는 상황이 있을 수 있다. 그럴 때는 객체에 std::enable_shared_from_this를 상속해주면 해결할 수 있다.

class A : public std::enable_shared_from_this<A> {

};

std::shared_ptr<A> pa1 = std::make_shared<A>();
std::shared_ptr<A> pa2 = pa1->get_shared_ptr();

std::cout << pa1.use_count() << std::endl;		// 2
std::cout << pa2.use_count() << std::endl;		// 2

이러면 정상적으로 두 shared_ptr이 사용 계수를 서로 공유하게 되고 double_free 버그를 방지할 수 있다. 

3. 순환 참조 shared_ptr

class A {
    int *data;
    std::shared_ptr<A> other;

   public:
    A() {
      data = new int[100];
    }

    ~A() {
      delete[] data;
    }

    void set_other(std::shared_ptr<A> o) { other = o; }
};

int main() {
    std::shared_ptr<A> pa = std::make_shared<A>();
    std::shared_ptr<A> pb = std::make_shared<A>();

    pa->set_other(pb);
    pb->set_other(pa);
    // 상호 참조 발생!
}

shared_ptr의 경우 참조 계수라는 것을 통해서 메모리에서 free를 할지 말지 결정한다. 근데 위와 같이 shared_ptr이 순환 참조를 할 경우, 참조 계수가 절대 0이 될 수 없다. (각자가 각자의 메모리를 가리키고 있기 때문에 서로 해제될 수 없다.)

 

이런 상황일 경우, weak_ptr을 통해서 설계를 할 것을 고려해야 한다. 

4. weak_ptr

weak_ptr은 shared_ptr이나 weak_ptr을 복사 생성해서 생성할 수 있고, weak_ptr이 무엇을 가리키던지 간에 사용 계수를 증가시키지 않는다. 

auto o = std::make_shared<A>();
std::weak_ptr<A> other = o;
// 반드시 shared_ptr이나 같은 weak_ptr을 복사생성해야 생성 가능
// weak_ptr을 생성해서 o를 참조해도 참조 계수 증가 X!

std::shared_ptr<A> b = o.lock();	// o.lock()은 shared_ptr이 가리키는 메모리가 존재하면 
					// 해당 shared_ptr을 리턴하고
                    // 존재하지 않는다면 아무것도 가리키지 않는 shared_ptr을 가리킨다.
if(b) {
	// 메모리가 해제되지 않은 경우(객체가 존재하는 경우)
}
else {
	// 메모리가 해제된 경우
}

따라서 위에서 설명한 순환 참조 shared_ptr 문제를 발생시키지 않는 방식이라고 할 수 있다. 

'프로그래밍 언어 > C++' 카테고리의 다른 글

C++ 11 / 스레드  (0) 2023.02.16
C++ / std::function  (0) 2023.02.16
C++ 11 / 스마트 포인터 unique_ptr  (0) 2023.02.15
C++ 11 / 보편적 참조(universal reference)와 forward  (0) 2023.02.15
C++ 11 / move함수  (0) 2023.02.15

댓글