임시 객체는 임시 객체일 뿐 No.1

이번 글에서는 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 의 끝까지 유지되도록 하면 더 좋지 않을까요 ? 이 문제에 대해 한 번 나름대로 고민해 보시면 재밌는 문제들을 발견할 수 있을 것입니다. ^^

Add a New Comment
or Sign in as Wikidot user
(will not be published)
- +
Unless otherwise stated, the content of this page is licensed under Creative Commons Attribution-ShareAlike 3.0 License