sliver__

[Mastering C++ Programming] - weak_ptr / Circular dependency 본문

CS/C++

[Mastering C++ Programming] - weak_ptr / Circular dependency

sliver__ 2022. 12. 7. 22:12
728x90
  • 지금까지 예제를 통해 shared_ptr의 긍정적인 측면에 대해 논의했습니다. 
  • 그러나 응용 프로그램 디자인에 순환 종속성이 있는 경우 shared_ptr이 메모리 정리에 실패합니다. 
  • 응용 프로그램 설계를 리팩터링하여 순환 종속성을 피하거나 weak_ptr을 사용하여 순환 종속성 문제를 해결할 수 있습니다.

  • https://www.youtube.com/watch?v=SVTLTK5gbDc. ( circular dependency 설명 )
  • A, B, C의 세 가지 클래스가 있다고 가정합니다. 
  • 클래스 A와 B에는 C의 인스턴스가 있고 C에는 A와 B의 인스턴스가 있습니다. 
  • 여기에 설계 문제가 있습니다. 
  • A는 C에 의존하고 C도 A에 의존합니다. 
  • 마찬가지로 B는 C에 의존하고 C도 B에 의존합니다.

  • 코드 예제는 아래와 같습니다.
#include <iostream>
#include <string>
#include <memory>
#include <sstream>
using namespace std;

class C;

class A {
      private:
           shared_ptr<C> ptr;
      public:
           A() {
                 cout << "\nA constructor" << endl;
           }

           ~A() {
                 cout << "\nA destructor" << endl;
           }

           void setObject ( shared_ptr<C> ptr ) {
                this->ptr = ptr;
           }
};

class B {
      private:
           shared_ptr<C> ptr;
      public:
           B() {
                 cout << "\nB constructor" << endl;
           }

           ~B() {
                 cout << "\nB destructor" << endl;
           }
 
           void setObject ( shared_ptr<C> ptr ) {
                this->ptr = ptr;
           }
};

class C {
      private:
           shared_ptr<A> ptr1;
           shared_ptr<B> ptr2;
      public:
           C(shared_ptr<A> ptr1, shared_ptr<B> ptr2) {
                   cout << "\nC constructor" << endl;
                   this->ptr1 = ptr1;
                   this->ptr2 = ptr2;
           }

           ~C() {
                   cout << "\nC destructor" << endl;
           }
};

int main ( ) {
                shared_ptr<A> a( new A() );
                shared_ptr<B> b( new B() );
                shared_ptr<C> c( new C( a, b ) );

                a->setObject ( shared_ptr<C>( c ) );
                b->setObject ( shared_ptr<C>( c ) );

                return 0;
}

 

 

  • 결과는 아래와 같습니다.
/a.out

A constructor

B constructor

C constructor

 

  • Destructor가 호출이 안된것을 볼 수 있는데 각 shared_ptr의 객체를 삭제하기 위해서는 dependency가 있는 객체가 삭제되어야 하기 때문입니다.
  • 이전 출력에서 shared_ptr을 사용했지만 개체 A, B 및 C에서 사용하는 메모리가 할당 해제되지 않았음을 확인할 수 있습니다. 
  • 호출되는 각 클래스의 소멸자를 보지 못했기 때문입니다. 
  • 그 이유는 shared_ptr이 내부적으로 참조 카운팅 알고리즘을 사용하여 공유 객체를 소멸시켜야 하는지 여부를 결정하기 때문입니다
  • shared_ptr은 자신이 참조하고 있는 객체(메모리 주소)에 대해 reference counting을 함으로써, 객체의 수명에 직접적으로 관여합니다.
  • shared_ptr 객체 하나가 소멸되더라도, 동일한 메모리 주소를 참조하고 있는 다른 shared_ptr 객체가 있으면 참조하고 있던 메모리 주소의 객체는 소멸되지 않습니다.
  • 객체 C를 삭제하지 않으면 객체 A를 삭제할 수 없기 때문에 여기서는 실패합니다.
  • 개체 C를 삭제하지 않으면 개체 B를 삭제할 수 없습니다.
  • 또한 개체 A와 B를 삭제하지 않으면 개체 C를 삭제할 수 없습니다.
  • 마찬가지로 객체 C를 삭제하지 않으면 객체 A를 삭제할 수 없고 객체 C를 삭제하지 않으면 객체 B를 삭제할 수 없습니다.
  • 결론은 이것이 순환 종속성 설계 문제라는 것입니다. 
  • 이 문제를 해결하기 위해 C++11부터 C++는 weak_ptr을 도입했습니다. 
  • weak_ptr 스마트 포인터는 strong reference가 아닙니다. 
  • 따라서 참조된 개체는 shared_ptr과 달리 언제든지 삭제할 수 있습니다.
  • 코드 예제는 아래와 같습니다.
 
#include <iostream>
#include <string>
#include <memory>
#include <sstream>
using namespace std;

class C;

class A {
      private:
           weak_ptr<C> ptr;
      public:
           A() {
                  cout << "\nA constructor" << endl;
           }

           ~A() {
                  cout << "\nA destructor" << endl;
           }

           void setObject ( weak_ptr<C> ptr ) {
                  this->ptr = ptr;
           }
};

class B {
      private:
           weak_ptr<C> ptr;
      public:
           B() {
               cout << "\nB constructor" << endl;
           }
           
           ~B() {
               cout << "\nB destructor" << endl;
           }

           void setObject ( weak_ptr<C> ptr ) {
                this->ptr = ptr;
           }
};

class C {
      private:
           shared_ptr<A> ptr1;
           shared_ptr<B> ptr2;
      public:
           C(shared_ptr<A> ptr1, shared_ptr<B> ptr2) {
                   cout << "\nC constructor" << endl;
                   this->ptr1 = ptr1;
                   this->ptr2 = ptr2;
           }

           ~C() {
                   cout << "\nC destructor" << endl;
           }
};

int main ( ) {
         shared_ptr<A> a( new A() );
         shared_ptr<B> b( new B() );
         shared_ptr<C> c( new C( a, b ) );

         a->setObject ( weak_ptr<C>( c ) );
         b->setObject ( weak_ptr<C>( c ) );

         return 0;
}

 

  • 결과는 아래와 같습니다.
./a.out

A constructor

B constructor

C constructor

C destructor

B destructor

 

++) Reference

1) [C++] shared_ptr circular reference - 스마트포인터 크로스 참조 피하기 : 네이버 블로그 (naver.com)

 

[C++] shared_ptr circular reference - 스마트포인터 크로스 참조 피하기

아... 저번에 치명적인 메모리 누수 문제를 만났다고 했잖아요? '모든 곳에서 shared_ptr을 썼는데 이상하...

blog.naver.com

2) [c++] weak_ptr :: 웅웅이의 지식창고 (tistory.com)

 

[c++] weak_ptr

이번장에서는 weak_ptr에 대해서 알아 보도록 합니다. shared_ptr를 구현하면서 참조 카운트에 영향을 받지 않는 스마트 포인터가 필요했는데 weak_ptr을 사용하면 shared_ptr가 관리하는 자원(메모리)을

jungwoong.tistory.com

 

728x90
Comments