728x90

Reference Counting 를 알고있어야 합니다. 잘 모르신다면 이쪽으로....

 

개요

순환 참조 문제는 비단 Reference Counting 뿐만이 아니라 다양한 영역에서 이를 피하는것이 매우 중요합니다.

 

설계적 관점에서, 서로 참조를 하는 두 객체가 있다면 의존 관계가 양방향이 되고

 

의존성(Dependency)이 커지기 때문에 코드 관리에 어려움이 생깁니다. (스파게티 요리사!)

 

멀티 스레드( or 프로세스 ) 환경에서는 Resource를 점유한 상태로 새 Resource의 요청이 "순환"하는 경우

 

교착 상태(Dead Lock)에 빠져 무한 대기 상태에 빠지게 됩니다.

 

다시 본론으로 와서, 순환 참조는 항상 안좋다! 라는 것을 기억하시고 살펴볼까요??

 

 

만약 두 객체 다음과 같이 서로를 참조하고 있다면 어떻게 될까요?

<그림1> 순환참조 예

바로 코드로 작성하여 확인 해보죠.

RefCounted 코드는 저번 Reference Counting 내용의 non-Intrusive 구현을 사용하였습니다. (대입 연산자만 추가)

class ObjectA
{
public:
	ObjectA(void) { std::cout << "::A Initailized!" << std::endl; }
	~ObjectA(void) { std::cout << "::A Deinitialized!" << std::endl; }
	RefCounted<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 : 2

===========================================

 

결과에서 알 수 있듯이 ObjectA와 ObjectB가 해제되지 않습니다. 해제 과정은 다음과 같은데요.

 

  1. countB가 해제 되며 ObjectB의 count는 1이됩니다. (stack이라 countB가 먼저 해제 됩니다!)

  2. countA가 해제 되며 ObjectA의 count는 1이 됩니다.

  3. ObjectB와 ObjectA를 강제로 해제하지 않는 이상 서로에 대한 참조 카운트가 내려가지 않아

     해제가 되지 않습니다.

 

메모리 관리를 용이하게 하려고 Reference Count를 사용했는데 메모리가 해제 되지 않아버리면 정말 난감 하겠죠?

 

이를 해결 하기 위해 많은 방법이 있지만, 1가지 방법을 소개하고자 합니다.

 

Automatic Reference Counting (Weak Reference)

 

용어 자체는 swift의 출현과 함께 나온 듯 합니다. Objective-C 시절 MRC (Manual Referenc Counting)에서 카운트를

 

내리거(해제)나 올리기(획득) 위해 개발자 직접 Releas, Retain과 같은 코드를 작성했다가, ARC가 출현하면서

 

컴파일타임에 컴파일러가 자동으로 추가해줘서 더이상 적지 않아도 되니 ARC다!! 라는것에 기인한다고 합니다.

(iOS 개발 해본 적은 없어 정확하지 않다면 알려주세요 ㅠㅠ)

 

사실 이게 중요한 것이 아니라, 눈여겨 봐야할건 바로 참조의 수준을 나눈 것입니다.

 

strong, weak 참조로 나누어 순환 참조를 해결하였습니다!!!

 

"약한 참조"는 실제 객체의 참조 카운트를 올리지 않는 것입니다!!

 

어? 그럼 c++이면 걍 한쪽은 raw pointer를 들고 있으면 되지 않나요?

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한지 알 길이 없습니다.

 

raw pointer만으로 약한 참조를 흉내낼 수 있지만 해당 객체를 사용할 수 있는지 없는지도 알아야 겠죠???

 

c++에서 그 구현체가 바로 weak_ptr 입니다.

 

구현

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;
};

: Count Helper class를 사용하여 strong count를 제어하도록 수정된 RefCouted

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;
};

WeakRefCounted의 get 메소드를 보시면 strong count가 0이면 nullptr를 반환 합니다. 따라서 이미 해제되었는지

 

알 수 있어서 매우 "스마트"합니다! 이제 ObjectA, ObjectB의 순환 참조 문제를 해결할 수 있습니다!

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!

===========================================

 

결론

weak_ptr을 적극적으로 쓰자!

728x90

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

Adaptor Pattern  (0) 2021.06.30
Factory Pattern - Simple Factory  (1) 2021.02.21
[C++] Reference Counting  (2) 2020.11.04
힙(Heap)과 완전 이진 트리(Complete binary tree)  (1) 2019.10.31
[c] SHA-1 구조 및 코드  (2) 2017.12.09

+ Recent posts