순환 참조 문제는 비단 Reference Counting 뿐만이 아니라 다양한 영역에서 이를 피하는것이 매우 중요하다. 설계적 관점에서, 서로 참조를 하는 두 객체가 있다면 의존 관계가 양방향이 되고 의존성(Dependency)이 커지기 때문에 코드 관리에 어려움이 생긴다. 멀티 스레드( or 프로세스 ) 환경에서는 Resource를 점유한 상태로 새 Resource의 요청이 "순환"하는 경우 교착 상태(Dead Lock)에 빠지게 된다.
class ObjectA
{
public:
ObjectA() { std::cout << "::A Initailized!" << std::endl; }
~ObjectA() { std::cout << "::A Deinitialized!" << std::endl; }
RefCounted<ObjectB> referenceB;
};
class ObjectB
{
public:
ObjectB() { std::cout << "::B Initailized!" << std::endl; }
~ObjectB() { std::cout << "::B Deinitialized!" << std::endl; }
RefCounted<ObjectA> referenceA;
};
int main(void)
{
RefCounted<ObjectA> countA(new ObjectA);
RefCounted<ObjectB> countB(new ObjectB);
std::cout << "countA : " << countA.getCount() << std::endl;
std::cout << "countB : " << countB.getCount() << std::endl;
// 여기서 순환 참조 발생!
countA.get()->referenceB = countB;
countB.get()->referenceA = countA;
std::cout << "countA : " << countA.getCount() << std::endl;
std::cout << "countB : " << countB.getCount() << std::endl;
return 0;
}
::A Initailized!
::B Initailized!
countA : 1
countB : 1
countA : 2
countB : 2
결과에서 알 수 있듯이 ObjectA와 ObjectB가 해제되지 않는다. 과정은 다음과 같다
- countB가 해제 되며 ObjectB의 count는 1이 된다. (stack이라 countB가 먼저 해제 된다)
- countA가 해제 되며 ObjectA의 count는 1이 된다.
- ObjectB와 ObjectA를 강제로 해제하지 않는 이상 서로에 대한 참조 카운트가 내려가지 않아 해제가 되지 않는다.
Automatic Reference Counting (Weak Reference)
"약한 참조"는 weak_ptr를 사용하여 실제 객체의 참조 카운트를 올리지 않는 것이다.
class ObjectA
{
public:
ObjectA(void) { std::cout << "::A Initailized!" << std::endl; }
~ObjectA(void) { std::cout << "::A Deinitialized!" << std::endl; }
ObjectB* referenceB;
};
class ObjectB
{
public:
ObjectB(void) { std::cout << "::B Initailized!" << std::endl; }
~ObjectB(void) { std::cout << "::B Deinitialized!" << std::endl; }
RefCounted<ObjectA> referenceA;
};
위의 예제는 dangling이 발생할 수 있다. 만약 다른 곳에서 ObjectB의 참조 카운트가 0이 되어 해제되었는데 referenceB를 사용하려 한다면 valid한지 알 길이 없다.
non-Intrusive 참조 카운터를 구현하며 count를 단순히 object의 count만 만들었는데 이제는 count가 두개가 된다 기존 object의 count는 strong count, 약한 참조용으로 weak count가 추가되어 "약한 참조자"가 참조를 하는 경우 strong count가 아닌 weak count가 올라간다. 따라서 피참조자 Object는 strong count가 0이 되면 해제되며, Count Object는 strong count도 0이고 weak count가 0이 되면 해제된다.
: 실제 count를 관장하는 count helper class
class RefCountHelper
{
public:
RefCountHelper(void)
: _strongCount(0)
, _weakCount(0)
{
}
int getStrongCount(void)
{
return _strongCount;
}
void addStrongCount(void)
{
++_strongCount;
}
int releaseStrongCount(void)
{
return --_strongCount;
}
int getWeakCount(void)
{
return _weakCount;
}
void addWeakCount(void)
{
++_weakCount;
}
int releaseWeakCount(void)
{
return --_weakCount;
}
private:
int _strongCount;
int _weakCount;
};
: Counter Helper class를 사용하여 strong count를 제어하도록 수정된 RefCounted
template<class Type>
class RefCounted
{
public:
template<class Type> friend class WeakRefCounted;
RefCounted(void)
: _pObject(nullptr)
, _pRefCount(nullptr)
{
}
RefCounted(Type* pObject)
: _pObject(pObject)
, _pRefCount(nullptr)
{
addCount();
}
RefCounted(RefCounted<Type>& pObject)
: _pObject(pObject.get())
, _pRefCount(pObject._pRefCount)
{
addCount();
}
RefCounted<Type>& operator = (const RefCounted<Type>& lvalue)
{
_pObject = lvalue._pObject;
_pRefCount = lvalue._pRefCount;
addCount();
return *this;
}
~RefCounted(void)
{
if (nullptr != _pObject)
{
reset();
}
}
Type* get(void)
{
return _pObject;
}
int getCount(void) const
{
return _pRefCount->getStrongCount();
}
private:
void reset(void)
{
if (0 >= releaseCount())
{
delete _pObject;
}
}
void addCount(void)
{
if (nullptr == _pRefCount)
{
_pRefCount = new RefCountHelper();
}
_pRefCount->addStrongCount();
}
int releaseCount(void)
{
return _pRefCount->releaseStrongCount();
}
private:
Type* _pObject;
RefCountHelper* _pRefCount;
};
: Count Helper class를 사용하여 weak count를 제어하는 WeakRefCounted
template<class Type>
class WeakRefCounted
{
public:
WeakRefCounted(void)
: _pObject(nullptr)
, _pRefCount(nullptr)
{
}
WeakRefCounted(RefCounted<Type>& pObject)
: _pObject(pObject.get())
, _pRefCount(pObject._pRefCount)
{
addCount();
}
WeakRefCounted<Type>& operator = (const RefCounted<Type>& lvalue)
{
_pObject = lvalue._pObject;
_pRefCount = lvalue._pRefCount;
addCount();
return *this;
}
~WeakRefCounted(void)
{
if (nullptr != _pObject)
{
reset();
}
}
Type* get(void)
{
if (0 >= _pRefCount->getStrongCount())
{
// 이미 해제 되었다.
return nullptr;
}
return _pObject;
}
int getCount(void) const
{
return _pRefCount->getWeakCount();
}
private:
void reset(void)
{
if (0 >= releaseCount() && 0 >= _pRefCount->getStrongCount())
{
delete _pRefCount;
}
}
void addCount(void)
{
_pRefCount->addWeakCount();
}
int releaseCount(void)
{
return _pRefCount->releaseWeakCount();
}
private:
Type* _pObject;
RefCountHelper* _pRefCount;
};
class ObjectA
{
public:
ObjectA(void) { std::cout << "::A Initailized!" << std::endl; }
~ObjectA(void) { std::cout << "::A Deinitialized!" << std::endl; }
void print(void) { std::cout << "print called!" << std::endl; }
WeakRefCounted<ObjectB> referenceB; // 약한 참조!
};
class ObjectB
{
public:
ObjectB(void) { std::cout << "::B Initailized!" << std::endl; }
~ObjectB(void) { std::cout << "::B Deinitialized!" << std::endl; }
RefCounted<ObjectA> referenceA;
};
int main(void)
{
RefCounted<ObjectA> countA(new ObjectA);
RefCounted<ObjectB> countB(new ObjectB);
std::cout << "countA : " << countA.getCount() << std::endl;
std::cout << "countB : " << countB.getCount() << std::endl;
countA.get()->referenceB = countB;
countB.get()->referenceA = countA;
std::cout << "countA : " << countA.getCount() << std::endl;
std::cout << "countB : " << countB.getCount() << std::endl;
return 0;
}
::A Initailized!
::B Initailized!
countA : 1
countB : 1
countA : 2
countB : 1
::B Deinitialized!
::A Deinitialized!
출처: https://nobilitycat.tistory.com/entry/C-Cyclic-Reference [고귀양이의 노트.:티스토리]
'프로그래밍 언어 > C++' 카테고리의 다른 글
C++ 바이트 패딩 (Byte Padding) (0) | 2022.07.26 |
---|---|
C++ 참조 대상 수 (Reference Counting) (0) | 2022.07.24 |
C++ 4가지 타입의 캐스팅 (0) | 2022.07.22 |
C++ 클래스 타입 업/다운 캐스팅 (Up-DownCasting) (0) | 2022.07.22 |
C++ 명시적 형변환/캐스팅 (explicit) (0) | 2022.07.22 |