다음과 같은 코드가 있습니다. 어떤 값이 출력될까요?
[코드 1]
int main() { int a = 0; a = ++a + ++a; cout << a << endl; }
[실행 1]
4
얼핏 생각하면 결과는 3이 나와야 하는데, 실제로는 4가 나오는 것을 확인할 수 있습니다.
위의 코드를 약간 풀어서 설명을 해 보겠습니다. 순서는 (B) > (D) > (C) > (A) 순으로 실행이 된다고 볼 수 있습니다.
a = // (A) (C)결과저장 ++a // (B) a증가 + // (C) (A)와(B)더함 ++a; // (D) a증가
결과가 4가 나오는 이유는 (B)+(D)의 수행 과정에서 "1+2"가 아닌 "2+2"로 연산을 하기때문에 발생하는 현상입니다(서로 다른 객체간의 덧셈이 이루어 지는 게 아니라, 똑같은 객체가 더해져 버림). 이는 C/C++ 언어의 ambiguity로 볼 수 있으며, 이렇게 실행 결과를 예측하기 힘들게 코딩을 하는 것은 그리 좋은 방법은 아니라고 볼 수 있습니다.
아무튼, 분석을 좀 해 봤습니다. C++로 클래스를 만들다 보면 가끔은 operator를 overloading해서 클래스를 설계하기도 하는데요, 위의 코드를 실행할 수 있도록 다음과 같이 operator를 overloading해 봤습니다( 참조 : http://en.wikipedia.org/wiki/Operators_in_C_and_C%2B%2B ).
[코드 2]
class VInt { protected: int _i; public: VInt() { _i = 0; } // A a : Default constructor VInt(const VInt& b) { _i = b._i; } // A a(b) : Copy constructor VInt(const int i) { _i = i; } // A a(i) : Conversion constructor ~VInt() { } // delete a : Destrutor VInt& operator = (const VInt& b) { _i = b._i; return *this; } // a = b : Basic assignment VInt operator + (const VInt& b) const { VInt res(_i + b._i); return res; } // a + b : Addition VInt& operator ++() { _i++; return *this; } // ++a : Prefix increment
VInt operator ++(int) { VInt res(_i); _i++; return res; } // a++ : Suffix increment operator int() const { return _i; } // (type)a : Cast };
[코드 1]에서 int를 VInt로 수정한 후 테스트를 해 보았습니다. 결과는 [실행 1]과 동일합니다.
[코드 3]
int main()
{ VInt a = 0; a = ++a + ++a; cout << a << endl; }
[실행 3]
4
여기에서 눈여겨 봐야 할 것은 [코드 2]의 Prefix ++ operator의 반환값 type입니다. 일반적으로 Prefix ++ operator는 reference를 반환하고, Suffix ++ operator는 object를 (복사해서) 반환하도록 설계하는 것이 일반적입니다.
- Prefix ++ operator : reference type 혹은 object type 둘다 반환값으로 가능.
- Suffix ++ operator : reference type으로 반환값을 줄 수 없음. 오로지 object type으로만 반환값을 줄 수 있음.
상기 [코드 2]에서 Prefix ++(빨간색 부분)의 반환 값을 reference type(VInt&) 가 아닌 object type(VInt)로 수정을 해서(&를 빼고) 다시 테스트를 해 보았습니다.
[코드 4]
VInt operator ++(int) // ... Prefix increment (반환값이 object type임. &을 뺌)
[실행 4]
3
객체의 복사가 이루어 지기 때문에 "2+2"(같은 object를 더하기)가 아닌 "1+2"(서로 다른 object간의 덧셈)가 수행이 되면서 4가 아닌 3이 출력되는 것을 확인할 수 있습니다.
[결론]
- operator overloading의 반환값을 reference로 하느냐, object(copy)로 하느냐는 클래스 설계자 마음대로임.
- 코딩을 할 때에는 ambiguity를 줄여 주게끔 하는 것이 상책. ^^
[다운로드]
reference by value라서 그런거 같은데 아닌가요?
a = ++a + ++a; <-- 증감연산자는 대입보다 먼저 일어나고,
증감연산자 갯수와 상관없이 증감이 먼저 일어난후 대입이 일어남으로 결국 동일한 변수에 동일한 값이 들어가기에 아래와 같이 연산된다.
4 = 2 +2;
이거 인듯 한데요?
a = a++ + a++ ; 이렇게 응용 해도 되고,
a = ++a + a++; 이렇게 응용 해도 되고
매년 한두번씩 이슈가 되는 이슈메이커네요 ;)
네, 하신 말씀이 맞습니다.
제가 말씀드리는 것은 a++의 반환 값의 type에 따라서 결과가 다르게 나올 수 있다는 거구요. pseudo code를 빌어서 설명해 보자면 다음과 같습니다.
- 결과가 4가 나오는 경우 : a++ 의 결과 값이 a 변수의 reference를 반환할 때
a = a + 1; a = a + 1; a = a + a;
- 결과가 3이 나오는 경우 : a++의 결과 값이 또다른 object를 반환할 때
temp1 = a + 1;
a = a + 1;
temp2 = a + 1;
a = a + 1;
a = temp1 + temp2;
increment(decrement) operator를 l-value와 r-value로 나누어서 설명한 스레드가 있네요. 이 글도 참조하심이 좋을 듯 합니다.
http://stackoverflow.com/questions/371503/why-is-i-considered-an-l-value-but-i-is-not
Using the above semantics, it is clear now why
i++
is no lvalue but an rvalue. Because the expression returned is not located ini
anymore (it's incremented!), it is just the value that can be of interest. Modifying that value returned byi++
would make not sense, because we don't have a location from which we could read that value again. And so the Standard says it is an rvalue, and it thus can only bind to a reference-to-const.However, in constrast, the expression returned by
++i
is the location (lvalue) ofi
. Provoking an lvalue-to-rvalue conversion, like inint a = ++i;
will read the value out of it. Alternatively, we can make a reference point to it, and read out the value later:int &a = ++i;
.Note also the other occasions where rvalues are generated. For example, all temporaries are rvalues, the result of binary/unary + and minus and all return value expressions that are not references. All those expressions are not located in an named object, but carry rather values only. Those values can of course be backed up by objects that are not constant.
The next C++ Version will include so-called
rvalue references
that, even though they point to nonconst, can bind to an rvalue. The rationale is to be able to "steal" away resources from those anonymous objects, and avoid copies doing that. Assuming a class-type that has overloaded prefix ++ (returningObject&
) and postfix ++ (returningObject
), the following would cause a copy first, and for the second case it will steal the resources from the rvalue:Object o1(++a); // lvalue => can't steal. It will deep copy. Object o2(a++); // rvalue => steal resources (like just swapping pointers)
아울러 rvalue references라는 개념도 같이 설명하고 있네요. 역시, 아무리 배워도 배울게 많이 있군요.
KLDP 사이트에 재미있는 질문이 올라 와서 분석해 본 것입니다( http://kldp.org/node/140130 ).
에고~ 이런 거는 쉽게 설명하기가 어려워... ㅠㅠ