Object 의 생명주기 (life cycle) 관리에 대해서 고민하다가 아래와 같이 작업을 해보게 되었습니다.
Reference counting 방식의 object 관리에 대한 내용인데, 기존에 object 또는 메모리 allocation 된 내용은 사실 관리하기가 무척 까다롭습니다.
다행히도 요즘에 C++에서 smart pointer 등의 feature를 정식으로 지원해서 좀 편해진 부분들이 있지만, 여전히 관리는 필요합니다. 물론 memory leak 때문 만은 아니고, dangling pointer 의 경우도 해당하죠.
해당 instance가 삭제되었는데 사용되는 경우들을 방지하는 것은 매우 중요하기 때문입니다.
이는 개발자가 머리 속으로 모든 것을 채크해 낼 수 없기 때문에, framework 이나 library 레벨에서 채크해주는 것은 매우 중요합니다.
shared_ptr과 weak_ptr로 대응 할 수도 있지만 약간 색다른 방법으로 접근 해보고자 했습니다.
ex)
class ITest {
public:
virtual bool doTest(){return true;} // 순수가상 함수로 만들고 싶었으나 그러면 설명할 코드가 길어질것 같아서 그냥 가상함수로 했습니다.
};
class IWork {
public:
virtual void doWork(){....}
}
class Framework {
public:
:
void setTest(ITest*test);
void setWork(IWork*work);
void run() {
if(pTest && pWork ) {
if( pTest->doTest() )
pWork->doWork();
}
}
ITest* pTest;
IWork* pWork;
}
void main()
{
Framework fr2;
ITest* pTest = new ITest;
IWork* pWork = new IWork;
fr2.setTest(pTest);
fr2.setWork(pWork);
delete pWork; <-- pWork가 delete되어도 개발자는 문제를 이 시점에 잡아낼 수 없습니다. fr2에서 사용 중인데도 말이죠.
fr2.run(); <-- 여기서 죽게 되죠..
}
이 예제 코드에서 문제를 해결하기 위해서 shared_ptr 과 weak_ptr 로 개선을 해보려 Framework class 내의 변수들을 weak_ptr로 변경 한다고 합시다.
class Framework {
public:
:
void setTest(std::shared_ptr<ITest> test);
void setWork(std::shared_ptr<IWork> work);
void run() {
if(pTest && pWork ) {
if( pTest->doTest() )
pWork->doWork();
}
}
std::weak_ptr<ITest> pTest;
std::weak_ptr<IWork> pWork;
}
이렇게 되면 아래와 같은 코드 작성시에 처리하기가 곤란해집니다.
class MyFramework : public Framework , ITest, IWork {
MyFramework () {
setTest (this); <-- 여기를 어떻게 해야 할지.
setWork(this);<-- 여기를 어떻게 해야 할지.
}
}
void main()
{
// case 1
MyFramework fr;
fr.run();
}
그래서 생각해본게 Object 와 ObjectRef 형식의 페어구조입니다.
코드는 아래와 같습니다.
즉 어떤 objectType A가 있다고 한다면.
A를 new 로 생성하고 사용하다가 만약 이 object의 pointer를 어디선가 참조하여 사용하려할때,
A object의 reference 를 증가시키고 이 Object는 Reference count를 채크하여 삭제될때 assert를 내줍니다.
이러면 사용중인 object가 삭제되는 시점을 찾을 수 있게 됩니다.
이 과정을 좀더 자연스럽고 어플리케이션 로직 개발자가 reference count를 의식하여 조작하지 않더라도 동작하도록 하는 부분에 대해서 고민이 필요한데, 이와 관련된 내용을 아래와 같이 정의 했습니다.
ObjectType * pObject = ObjectType;
ObjectRef<ObjectType> ref = *pObect;
이와 같은 형태로 사용하게 되면, 기존에 smart pointer 와 비슷한 형식이 됩니다.
사용상 편의성 역시 큰 차이가 없을것 으로 보여지고 말이죠.
Object와 ObjectRef 의 관계 구현은 아래와 같습니다.
<RefObject.h>
#ifndef __OBJECT_REF_H__
#define __OBJECT_REF_H__
class Object
{
public:
Object();
virtual ~Object();
private:
void addRef(){ __ref++; }
void relRef(){ __ref--; }
int getRef(){ return __ref; }
private:
int __ref = 0;
friend class _Ref;
};
class _Ref
{
public:
virtual ~_Ref();
_Ref();
_Ref(Object& obj);
void set(const Object& obj);
void reset(); // reference를 reset 하는 기능
operator bool();
protected:
Object*__pObject = NULL;
};
template<typename T>
class ObjectRef : public _Ref
{
public:
ObjectRef(){}
ObjectRef(T& o) :_Ref(o){}
T* get(){ return dynamic_cast<T*>(__pObject); }
T* operator ->(){ return dynamic_cast<T*>(__pObject); }
ObjectRef& operator = (const T& obj)
{
set(obj);
return *this;
}
};
#endif //#ifndef __OBJECT_REF_H__
// cpp file
#include <memory>
#include <assert.h>
#include <stdio.h>
#include "objectRef.h"
Object::Object(){}
Object::~Object()
{
assert(__ref <= 0);
}
_Ref::~_Ref()
{
if (__pObject)
{
__pObject->relRef();
__pObject = NULL;
}
}
_Ref::_Ref()
{
}
_Ref::_Ref(Object& obj)
:__pObject(&obj)
{
obj.addRef();
}
void
_Ref::set(const Object& obj)
{
if (__pObject)
{
__pObject->relRef();
}
__pObject = const_cast<Object*>(&obj);
__pObject->addRef();
}
void
_Ref::reset()
{
if (__pObject)
{
__pObject->relRef();
__pObject = NULL;
}
}
_Ref::operator bool()
{
return (__pObject != NULL);
}
위 구현 로직을 바탕으로 테스트용 셈플을 작성해봤습니다.
<test.cpp>
#include <stdio.h>
#include "RefObject.h"
namespace
{
class Do : virtual public Object
{
public:
virtual void onDo(){ printf("onDo()\n"); }
};
class Done : virtual public Object
{
public:
virtual void onDone(){ printf("onDone()\n"); }
};
} //namespace
void
main()
{
Do* pDo = new Do;
Done* pDone = new Done;
pDo->onDo();
pDone->onDone();
ObjectRef<Do> doRef(*pDo);
ObjectRef<Done> doneRef(*pDone);
doRef->onDo();
doneRef->onDone();
doRef.reset();
delete pDo;
delete pDone; //<-- error occurred
}
main 에서 doRef.reset() 을 Reference 를 reset한 경우에는 delete가 정상적으로 되지만, 그렇지 않은경우 delete 시에 assert가 발생합니다.
즉, 사용하고 있는 곳이 있는데 어디선가 delete를 시도 한다면 reset을 발생시켜주는 코드입니다.
<test2.cpp>
#include <memory>
#include <assert.h>
#include <stdio.h>
#include "objectRef.h"
namespace {
class IDo : virtual public Object
{
public:
virtual void onDo() = 0;
};
class IDone : virtual public Object
{
public:
virtual void onDone() = 0;
};
class MyDo : virtual public Object, virtual public IDo, virtual public IDone
{
public:
virtual void onDo()
{
printf("hello !\n");
}
virtual void onDone()
{
printf("bye !\n");
}
};
class Job
{
public:
void setJob(IDo* job)
{
instDo = *job;
}
void setJobNoti(IDone* jobnoti)
{
instDone = *jobnoti;
}
void doWork()
{
if (instDo)
{
instDo->onDo();
if (instDone)
instDone->onDone();
}
}
void reset()
{
instDo.reset();
instDone.reset();
}
ObjectRef<IDo> instDo;
ObjectRef<IDone> instDone;
};
}
void
main(void)
{
std::unique_ptr<MyDo> pMyDo(new MyDo);
Job j;
j.setJob(pMyDo.get());
j.setJobNoti(pMyDo.get());
j.doWork();
// j.reset();
delete pMyDo.release();
j.doWork(); // <-- error occurred
}