소멸자에서 virtual 함수동작이 다르기 때문에 종료 함수를 따로 빼줘야 할까요?
글쓴이: karzia / 작성시간: 목, 2011/09/15 - 8:23오후
class Object
{
public:
Object();
virtual ~Object();
void Close(){OnClose();};
protected:
virtual void OnClose(){};
};
class Item: public Object
{
public:
Item();
virtual ~Item()
{
Close();
}
virtual void OnClose()
{
SendMessageToServer("Close");
}
};
void main()
{
Item * pItem = new Item();
delete pItem;
}
위와 같은 테스트 코드가 있다고 한다면,
delete 시에 Close() 함수가 Object::OnClose()를 호출하는 사태가 발생하게 됩니다.
결국 delete 전에 Close() 함수를 불러주거나 해야 하는데, 결국 destruct에서 정리하지 못하게 되기 때문에 코드가 복잡해지게 되면,
언젠가는 새는 구멍이 나오게 됩니다.
destruct에서 해결할 수 있는 방법이나 좋은 테크닉이 없을까요?
Forums:
생성자와 소멸자에선 가상함수를 호출하면 안됩니다.
생성자와 소멸자에선 가상함수를 호출하면 안됩니다. http://www.informit.com/articles/article.aspx?p=397656
가상함수를 호출하는 일반함수도 마찬가지로 생성자와 소멸자에선 사용할 수 없습니다.
어차피 모든 자식 클래스가 OnClose()를 정의하고 소멸자에서 Close()를 호출한다면 차라리 OnClose()의 내용을 destructor로 이동시키는 것이 낫지 않을까요
말씀하신데로 그렇게 하고 싶은데,
해당 코드는 예일 뿐이고,
제가 작업하는 시스템에서는 Close() 에서 하는 일이 단순히 OnClose()를 부르는게 아니라 다른 로직들과 child item들에 대한 처리 로직들이 있어서,
이 close()를 이용하여 OnClosed() 가 불려야만 정상적으로 돌아가게 됩니다. ㅠ_ㅠ
결국 현재로서는 Close() API를 delete 전에 부를 수 밖에 없어서 문제가 되더군요.
subsrciber 객체(즉 listener) 를 달아서 처리하는 방법
그래서 고민해봤던것이 subsrciber 객체(즉 listener) 를 달아서 처리하는 방법을 고민해봤습니다.
즉,
class IObjectListener
{
public:
virtual void OnFinalized()=0;
};
class Object
{
public:
Object(){};
virtual ~Object();
void Close(){__pListener->OnFinalized();};
void SetListener(ObjectListener * pListener){__pListener = pListener;};
private:
IObjectListener * __pListener;
};
class Item: public Object ,IObjectListener
{
public:
Item(){SetListener(this);};
virtual ~Item()
{
Close();
}
virtual void OnFinalized()
{
SendMessageToServer("Close");
}
};
void main()
{
Item * pItem = new Item();
delete pItem;
}
이런 형식도 고민 중에 있습니다.
Item객체가 destruct될 때
Item객체가 destruct될 때 Object::OnClose()가 불리는 것이 아니라 Item::OnClose()가 불릴텐데요...
한번 확인해 보세요.
네 Item::OnClose() 가 불리는 것이 문제입니다.
네 그게 문제라서 다른 해결방법이 없나 찾아보고 있는중입니다.
결국 delete 와 Close() 를 따로 분리하는게 가장 혼란지 적은 방법인듯합니다.
사용시에는 좀 불편하겠지만요.
그렇다면 원래 의도는 Object::OnClose()가 호출되는 것인가요?
만일 그러하다면 단순히 non-virtual function을 구현하면 될텐데요.
네.. 의도는 상속받은 OnClose가 destruct 때 불리기를 바라는 거죠.
이것저것 해봤는데요.
어떤 꽁수를 부려서 delete시에 derived class의 OnClose가 불린다하더라도, 결국 derived class의 소멸자가 불린후 base class의 destruct가 불리는 시점에 OnClose가 불릴 것이기 때문에,
단순히 method만 불릴 뿐이라서 , 위의 시도 자체가 좋지 못한 case 더군요.
결론 : destruct에서 할수 있는 일은 다하자.!
위와 같은 경우가 발생하는 경우라면, 명시적인 Close() 나 Destory() 함수를 만들어서 제공하자!.
cyclic reference의 문제 같네요. 사실
cyclic reference의 문제 같네요. 사실 상 구분되는 두 개의 역할들이, 각각의 인스턴스로 나뉘어야 할 것들이 "상속"에 의해 하나의 인스턴스에 몰려있다보니 누가 누구를 어떻게 참조하는건지가 모호하게 되어버리는 거죠.
제시하신 내용만으로 확신할 순 없지만, 추측컨대 Object와 Item은 서로 다른 Layer에 속하는 객체들 같습니다. 가령 Object는 서버와의 통신 채널을 담당하는 LowlevelChannel, Item은 그 통신 채널 위에서 동작하는 ProtocolClient라고 볼 수 있겠지요. 이렇게 서로 다른 레이어에 속하는 객체들은 서로를 참조할 순 있겠지만, 상속을 시키는 건 별로 좋은 방식이 아닌것 같습니다.
저라면 1) Object와 Item의 상속을 제거한 뒤, 2) 둘 사이의 상호 참조를 일방향 참조로 바꿀 수 있는지 시도해보겠습니다.
만약 일방향 참조로 바꿀 수 있다면 그 참조를 ownership 관계로 간주하면 그대로 자연스럽게 해결될 것이고,
만약 그렇지 않고 상호참조가 여전히 필요하다면 둘 중 어느 것이 ownership 관계이고 어느 것이 단순 참조인지를 결정한 뒤 그에 따라 소멸 관계를 처리하겠습니다.
int close(int
int close(int socketdescriptor);
class LowlevelChannel {
int sd;
public:
LowlevelChannel(char *addr) { ... }
~LowlevelChannel() { close(sd); }
int write(char* data) { ... }
};
class ProtocolClient {
LowLevelChannel chan;
public:
ProtocolClient() : chan(MY_SERVER_ADDRESS) { chan.write("HELO"); }
~ProtocolClient() { chan.write("BYE"); }
}
gilgil.net
constructor와 destructor에서 virtual function은 주의를 해서 사용을 하면 됩니다(호출은 됩니다).
virtual function은 constructor 및 destructor에서 호출을 하는 경우 일반 method call과 같이 불리게 됩니다(virtual call을 하지 않음).
http://www.gilgil.net/9239
www.gilgil.net
댓글 달기