본문 바로가기

개발 Note/Codes

C++ object 관리를 위한 ObjectRef 구조 설계

반응형



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

}







'개발 Note > Codes' 카테고리의 다른 글

[Andriod] Timer and TimerTask  (0) 2020.10.22
Singleton class 설계  (0) 2020.10.07
sorting algorithms  (0) 2014.02.26
c++ while 문 - 잘 안쓰는 표현  (0) 2013.10.23
UML2 Sementics  (0) 2012.01.11