보통 로그를 관리하는 Log 클래스와 어플을 관리하는 App 클래스는 Singleton pattern으로 하나만 생성해서 사용하는 것이 일반적인 경우가 있습니다. 이러한 경우, Singleton 객체간의 의존선이 존재할 때, 나중에 프로그램이 종료될 때 예상치 못한 에러가 야기될 수 있습니다. 아래 코드를 보고 프로그램 종료시 에러가 왜 나는지 예측해 보세요. ^^
#include <stdio.h> #include <string.h> class Log { FILE* fp; private: Log() { fp = fopen("test.log", "wb"); } virtual ~Log() { fclose(fp); fp = NULL; } public: void write(const char* msg) { fwrite(msg, 1, strlen(msg), fp); }; public: static Log& instance() { static Log g_log; return g_log; } }; class App { private: App() { //Log::instance().write("App::Appn"); } virtual ~App() { Log::instance().write("App::~Appn"); } public: void start() { Log::instance().write("App::startn"); } static App& instance() { static App g_app; return g_app; } }; int main() { App::instance().start(); return 0; }
댓글 14
-
홍문화
2011.10.09 19:43
-
홍문화
2011.10.12 11:04
경문님 왜 정답 안알려 주세요? ㅋ
-
하상은
2011.10.12 12:18
Log 와 App 중 어떤게 먼저 소멸될지가 문제가 되겠군요ㅋ 소멸자중에서도 프로그램 종료와 함께 소멸할경우 각별한 주의가 필요합니다ㅎㅎ
-
홍문화
2011.10.12 12:55
궁금해서 테스트 해봤더니 아래처럼 해주니 에러가 안나네요.
인스턴스 접근이 가능한것을 보니 static으로 생성 되어서 소멸자만
호출하고 메모리 해제는 하지 않은 상태가 되는것 같네요.
void write(const char* msg)
{
if(fp == NULL) return;
fwrite(msg, 1, strlen(msg), fp); }; -
이경문
2011.10.12 15:03
아... 홍문화님, 죄송합니다. ^^
본 문제는 Log 객체가 해제되고 난 이후에 App 객체에서 Log객체를 사용하기 때문에 발생하는 문제입니다. 하상은님이 말씀하신 것처럼 write내부에서 처리를 해 줘도 되지만, 좀 더 근본적인 해결 방안을 얘기해 보겠습니다.
지금 만들고 있는 라이브러리가 있는데, 제가 짠 것만 해도 대략 1만라인 정도가 됩니다. 전역 객체(static 객체 포함)는 가급적 쓰지 않으려고 하지만, 위의 예처럼 Log 객체는 거의 전역으로 사용하는 것이 편합니다. 어쩔 수 없이 전역 객체를 사용을 하는 경우가 있는데, 전역 객체의 의존성 문제때문에 문제가 발생할 수가 있습니다. 위의 코드는 아주 쉽게 예를 든 것이고, 소스 라인 수가 많아 지면 이러한 버그를 찾기가 어렵고, 시간도 무지오래 걸리죠. third party library와 연계를 하고 있어서 외부 소스를 건드릴 수도 없구요. ^^
-
이경문
2011.10.12 15:05
일반적으로 전역 객체는 다음과 같은 순서로 생성과 해제가 이루어 집니다.
1. non-local static object들을 우선 생성(linker가 알아서 순서가 결정됨).
2. main 함수 진입.
3. local static object 접근할 때 object 생성(프로그램 코드에 따라서 순서가 결정됨).
4. main 함수 끝.
5. 1, 3 과정에서 생성된 object를 해제.
5번에서 object들은 생성된 순서의 반대 순서로 해제가 됩니다(stack에서 관리).
-
이경문
2011.10.12 15:11
저는 다음과 같은 방법을 사용하였습니다.
1. Dependency라는 template class를 만든다.
// ---------------------------------------------------------------------------- // Dependency // ---------------------------------------------------------------------------- template <class T> class Dependency { public: /// class T must have static void dependency() Dependency() { T::dependency(); } virtual ~Dependency() {} };
2. Log 클래스에서 depencency()하는 method를 제공한다.
// ---------------------------------------------------------------------------- // Log // ---------------------------------------------------------------------------- class Log { ... public: static void dependency() { instance(); } };
3. App 클래스가 Log클래스와 의존성이 있다고 명시해 놓는다.
/ ---------------------------------------------------------------------------- // App // ---------------------------------------------------------------------------- class App : public Dependency<Log> { ... };
이렇게 해 놓으면 App static object가 해제되기 이전에 Log static object가 먼저 해제되는 것을 방지할 수 있습니다.
ps : 방법은 여러가지입니다. 제가 사용한 방법이 최선이라고 할 수는 없습니다. ^^
-
홍문화
2011.10.12 17:10
경문님 덕에 C++ 공부를 손 안대고 코풀기? 하고 있습니다. ㅋ
실무에서 고생한 부분을 공유 하는 일이 쉬운게 아닌데...
감사합니다. ^^;
-
이경문
2011.10.12 16:53
네, 말씀하신것처럼 App constructor에서 Log 관련 코드를 추가해도 됩니다.
이러한 방식을 사용해도 무관하구요, 저처럼 Dependency라는 template class를 이용하는 것도 하나의 방법입니다.
참고로 객체의 생성과 해제는 다음의 순서로 이루어 집니다.
[생성]
1. 메모리 할당(malloc)
2. constructor code 수행(상위 클래스부터).
3. virtual method table 변경(상위 클래스부터).
4. 2~3번 과정이 반복됨.
[해제]
1. virtual method table 변경(하위 클래스부터).
2. desturcotr code 수행(하위 클래스부터).
3. 1~2번 과정이 반복됨.
4. 메모리 해제(free).
ps : C++의 constructor 및 destructor에서 virtual method를 call하는 경우 (VMT를 참고하는) indirect call을 하는 것이 아니라 (VMT를 사용하지 않는) direct call을 하게 됩니다.
-
홍문화
2011.10.12 15:56
켘 상당히 복잡하네요.ㅋ
상속을 통해서 Log를 먼저 생성한다는 말씀 같은데...
그냥 아래 생성자에서 주석만 해제하면 Log가 App보다 먼저 생성될거 같은데요.
App() { //Log::instance().write("App::Appn"); }
객체 생성은 1)메모리 할당과 함께 2)생성자 호출이 완료되어야 이루어집니다.
객체 소멸은 1)소멸자 호출과 함께 2)메모리 해제가 완료되어야 이루어집니다.
즉, 소멸자가 호출 되었다고 객체 소멸이 완료 된것은 아닙니다. -
하상은
2011.10.12 18:40
저는 위의 예제와 같은경우 App 에서 Log 클래스의 인스턴스 포인터 하나를 직접 관리하도록 하는 형태나 프로그램 종료시 명시적으로 순서에 맞춰 해제해주는 방법을 이용했었지만 코드가 덩치가 커지고 많은사람이 협업할땐 힘든일이죠..
"코드를 잘못사용하기 힘들게 설계하라" 는 측면에서 경문님이 소개해주신 예가 좋은것같습니다 객체지향적이고 우아하네요ㅋ
-
이경문
2011.10.12 23:50
@하상은 네, 감사합니다. ^^
Template class 이름을 조금 바꾸어 봤습니다. 의미 전달이 좀 더 명확할 듯... ^^
http://www.gilgil.net/10260#comment_10429
-
이경문
2011.10.13 11:02
"Interface"의 "I"가 아니라 나(自)를 뜻하는 "I"인데, 우연히 어쩌다 보니... ㅋㅋㅋ
-
하상은
2011.10.13 09:23
앞에붙은 I 가 절묘하네요.. Interface 인듯 대명사인듯ㅋㅋㅋ
.