C++ 이야기 열일곱번째: 임시 객체는 임시 객체일 뿐

Posted at 2008. 3. 3. 09:18 // in S/W개발/C++ 이야기 // by 김윤수


[이글의 최신 Update 문서는 항상 여기에서 확인할 수 있습니다]

오랜만에 C++ 이야기를 포스팅합니다. 제가 그 동안 상당히 외도를 많이 했었죠 ? 원래 블로그를 시작할 때만 해도 소프트웨어 개발 이야기를 전문적으로 다루려고 했는데, 제가 워낙 다방면에 관심이 많다 보니 여기 저기 찌르고 다니느라 이제서야 제 전문 분야로 돌아오게 됐습니다. ^^ 제 블로그에서 소프트웨어 개발 이야기와 관련된 글을 기대하시는 독자분들께는 정말 죄송하지만 제 성격상 어쩔 수가 없더군요.

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

제 글이 유익하셨다면 오른쪽 버튼을 눌러 제 블로그를 구독하세요. ->
블로그를 구독하는 방법을 잘 모르시는 분은 2. RSS 활용을 클릭하세요.
RSS에 대해 잘 모르시는 분은 1. RSS란 무엇인가를 클릭하세요.

다음글 예고편: 바람직한 소스 코드 관리 전략 #2
다음글도 기대해 주세요~~~ :)