해당 코드에 순간적으로만 존재하고, 값을 반환하는 함수가 예가 된다.
lvalue : =(대입연산자)의 좌변, 우변에 모두 사용가능한 변수로 이름이 있으며 일정 시간동안 lifetime을 갖는다.
참조를 반환하는 함수가 예가 된다.
xvalue, prvalue, glvalue 가 더 있으나 일단은 위 두가지만 이해해 보도록 하자.
Detail Reference : http://en.cppreference.com/w/cpp/language/value_category
객체의 lifecycle 에 대해 알아보자.
전역 객체 p1은 main() 함수보다 먼저 생성된다.
지역 변수 p2 는 자신을 선언한 main() 함수 block 을 벗어날 때 소멸된다.
main()함수 안에서 Point(); 코드는 임시객체 생성을 의미하며,
자신이 포함된 문자(full expression)이 끝날 때 소멸된다.
임시 객체는 이름이 없기 때문에, unnamed object 라고도 불린다.
임시 객체에 대해 좀 더 알아보자.
main() 함수내에 선언된 p 객체가 만약 foo() 함수에 전달하고 사용될 필요가 없는 객체라면?
이렇게 단순히 인자로 전달하기 위한 객체는 임시객체로 만들어 사용하는 것이 바람직하다.
foo() 에서 반환하는 지역 변수 p는
실제로는 foo() 함수가 끝남과 동시에 소멸된다.
따라서, 반환할 임시객체를 생성하고 p 를 소멸하고 임시객체를 반환한다.
임시객체는 복사생성자를 통해서 생성된다.
Point p를 만들고 다시 임시 객체를 생성하는 중복 작업이 성능 저하를 가져오기 때문에
바로 임시객체를 만들어서 전달하는 것이 효과적이다.
이 기법을 RVO(Return Value Optimization)이라고 부른다.
하지만 RVO 기법을 사용하지 않고 이전 코드를 사용하더라고 컴파일러가 RVO 를 적용한다.
Microsoft VC 같은 경우엔 release 모드로 컴파일 할 경우, 컴파일러가 알아서 RVO를 적용한다.
최근의 컴파일러들은 이름있는 객체도 최적화할 수 있다.
이런 기법을 NRVO(Named RVO) 라고 부른다.
임시 객체와 lvalue에 관해 살펴보자.
제일 처음에 설명한 것을 다시 생각해보면 rvalue, lvalue 라는 것이 있고,
rvalue 는 =(대입연산자)의 우측에만 올 수 있으며 이름이 없다.
또한 해당 코드에 순간적으로만 존재하고, 값을 반환하는 함수가 예가 된다.
반면 lvalue 는 =(대입연산자)의 좌변, 우변에 모두 사용가능한 변수로 이름이 있으며 일정 시간동안 lifetime을 갖는다.
참조를 반환하는 함수가 예가 된다.
위 예제에서 foo() 함수는 값(value)을 반환하기 때문에 임시 객체를 생성하여 반환될 것이다.
전역변수 p의 복사본이 임시객체로 생성되어 반환되는 것이다.
따라서 main() 함수에서 foo().x = 10; 코드는
좌변에 rvalue(임시객체) 가 오는 형태가 되므로 컴파일 에러가 발생한다.
하지만 foo1() 함수는 참조(reference) 를 반환하기 때문에
전역객체 p의 주소가 그대로 반환되며 이것은 lvalue 이다.
lvalue 는 =(대입연산자)의 좌변에 올 수 있어 foo1().x = 10; 코드는 정상적으로 동작하고
실제 x의 값이 10으로 변경된 것을 볼 수 있다.
rvalue, lvalue 에 대해 조금 더 알아보자.
10 = n1; 이란 코드를 본적이 있는가?
10이 이름을 갖는 변수인가?
여기서 10은 rvalue 로 =(대입연산자) 좌변에 올 수 없다.
하지만 이름을 갖는 변수 n1, n2는 lvalue 이기에 n1 = 10; n2 = n1; 코드는 정상이다.
또한 이전 예제에서도 설명했듯이,
값을 반환하는 함수는 임시객체를 생성하여 전달하고 이것은 rvalue 이다.
따라서 foo().x = 10; 은 컴파일 에러를 발생시킨다.
lvalue, rvalue 의 규칙을 살펴보자.
규칙
1. C++ 일반 참조는 lvalue 만 참조할 수 있다.
2. const reference 는 rvalue, lvalue 모두를 참조할 수 있다!!
3. C++11 에서는 rvalue reference(&&)를 제공하여 rvalue 만 참조할 수 있도록 지원한다.
따라서, C++11 에서 공식적으로 &는 그냥 reference가 아닌 lvalue reference 라고 부르고
&& 는 rvalue reference 라고 부른다.
참조가 사용되는 함수와 Overloading 규칙을 살펴보자.
main()함수 내의 foo(n); foo(10); 코드를 먼저 살펴보면,
이전 예제에서 설명했듯이, const int& 는 rvalue, lvalue 를 모두 참조할 수 있다.
따라서 lvalue 인 n의 참조를 가리킬 수 있는 foo(int&);이 호출 우선순위가 높지만 이 함수가 없을 경우
foo(const int&);가 호출될 수 있다.
또한 rvalue 인 10은 rvalue 만을 가리키는 &&(rvalue reference) 를 인자로 받는 foo(int&&); 가 호출 우선순위가 높지만,
이 함수가 없을 경우 foo(const int&); 가 호출될 수 있다.
하지만 const int c 는 반드시 foo(const int&); 가 없으면 에러가 발생하는 것에 유의하자.
중요한 것은 다음 코드이다.
int&& r = 10; 에서
10은 rvalue 이고 r은 이름있는 참조 변수인 lvalue 이다.
따라서 foo(r); 코드는 foo(int&);이 호출된다.
이름이 있는 rvalue reference 는 lvalue 인 것이다.(named rvalue reference)
rvalue 와 rvalue reference 를 혼동하기 쉬운데,
10은 rvalue 이고 r 은 rvalue reference(결국 lvalue) 인 것이다.
그렇다면 r을 foo(int&&) 함수로 호출하고 싶다면?
Casting 해야 한다!
foo(static_cast
댓글 없음:
댓글 쓰기