이번 글에서는 C++에서 임시 객체와 관련된 재밌는 문제를 다뤄볼까 합니다. 자~ 우선 다음 코드를 한 번 보시죠.
void f(X a1, X a2) { extern void g(const X&); X z; // . . . z = a1 + a2; g(a1 + a2); // . . . }
위 코드에서 임시 객체가 쓰이는 부분을 찾으실 수 있으시겠어요 ? 예~ 두 군데가 있습니다. a1 + a2 값을 z 에 assign 하기 전과 g() 함수의 인자로 넘겨줄 때 임시 객체가 사용됩니다. 이 임시 객체라는 녀석을 곰곰히 들여다 보고 있으면 자연스럽게 떠오르는 질문이 하나 있습니다.
"이 녀석은 언제 destructor 가 불리지 ?"
궁금증을 해결하려면 직접 부딪혀 보는 것보다 좋은 방법은 없을 것입니다. 다음과 같은 간단한 프로그램을 실행시켜 보기로 하겠습니다.
// X.h #ifndef X_H #define X_H class X { private: int _i; public: X(int i = 0) : _i(i) {} ~X(void); X operator + (const X& x1); int get(); }; #endif // X.cpp #include "X.h" #include <iostream> using namespace std; X::~X() { cout << "~X() called" << endl; } X X::operator +(const X &x1) { return X(_i + x1._i); } int X::get() { return _i; } // main.cpp #include "X.h" #include <iostream> using namespace std; int main(void) { X x1(10); X x2(20); X z; z = x1 + x2; cout << "The first use of temporary object" << endl; cout << "x1 + x2 = " << (x1 + x2).get() << endl; cout << "The second use of temporary object" << endl; cout << endl << endl << "Exitting..." << endl; }
이걸 컴파일&빌드한 후 실행해보면 다음과 같이 출력되는 걸 확인하실 수 있을 것입니다.
~X() called
The first use of temporary object
x1 + x2 = 30
~X() called
The second use of temporary object
Exitting...
~X() called
~X() called
~X() called
자~ 이제는 대충 감이 잡히시죠 ? 위 결과를 봐서는 아무래도 임시 객체가 사용된 statement의 끝(end of statement; EOS)에서 destructor가 호출되는 것 같습니다. 임시 객체의 lifetime 은 임시객체가 사용된 statement 의 끝까지라고 보면 될 것 같습니다.
그럼 다음과 같은 코드는 어떻게 될까요 ? operator const char*() 의 구현이 따로 복사본을 리턴하는 것이 아니라 내부 char 배열에 대한 pointer 만 넘겨 주도록 구현되어 있다고 가정해 보시기 바랍니다(보통은 이런식으로 optimize 를 해서 구현하기 마련이죠).
class String { // ... public: friend String operator+(const String&, const String&); // ... operator const char*(); // conversion operator to C-style string }; void f(String s1, String s2) { const char* p = s1+s2; // 바로 이 부분이 문제!! printf("%s", p); // ... }
언뜻 보기에는 위 코드는 아무런 문제가 없어 보이지만 임시 객체를 사용하는 부분에서 포인터 p 는 s1+s2의 결과를 담고 있는 임시 객체 내부 char 배열을 가리키게 됩니다. 그리고나서 printf()의 인자로 p 가 넘어가기 전에 s1+s2의 결과를 담고 있던 임시 객체가 destroy 됩니다. 즉, p는 더이상 valid 한 주소를 가리키지 않는 것이지요. f() 함수 코드를 다음과 같이 고치면 아무런 문제가 없을 것입니다.
void f(String s1, String s2) { printf("%s", (const char*)(s1+s2)); // ... }
조금은 당황스럽긴 하시겠지만 이것이 C++의 규칙이라는군요. 그러니 임시 객체를 쓰실 때는 각별히 주의하셔야겠습니다. 다음과 같이 기억하고 계시면 위와 같은 실수는 막을 수 있지 않을까 합니다. ^^
"임시 객체는 임시 객체일 뿐 계속 쓰지 말자"
그런데, 이런 걸 보면 왜 언어를 이런식으로 정의해 놓았을까라는 생각이 들지 않으세요 ? 임시 객체의 lifetime이 좀 더 오랫동안 예를 들어, block 의 끝까지 유지되도록 하면 더 좋지 않을까요 ? 이 문제에 대해 한 번 나름대로 고민해 보시면 재밌는 문제들을 발견할 수 있을 것입니다. ^^
Post preview:
Close preview