본문 바로가기

Programming/CPP11&14

[C++11] RValue Reference(4)

반응형

6. RValue Reference의 추가적인 특징

RValue Reference의 추가적인 특징들입니다.

이미 설명한 것과 같이 RValue의 Move Sematics와 Perfect Fowarding은 성능 향상에 많은 도움을 제공합니다.

그 외 RValue Reference의 추가적인 특징은 다음과 같습니다.

1) RValue Reference와 LValue Reference를 받는 함수를 오버로딩 가능하다.

RValue와 LValue는 다르게 취급되기 때문에 RValue Reference와 LValue Reference를 전달받는 함수는

오버로딩이 가능합니다.

이미 생성자와 대입 연산자를 통해서 알아 본 내용입니다.

#include 
using namespace std;

// A class that contains a memory resource.
class MemoryBlock
{
};

void f(const MemoryBlock&)
{
	cout << "In f(const MemoryBlock&). This version cannot modify the parameter." << endl;
}

void f(MemoryBlock&&)
{
	cout << "In f(MemoryBlock&&). This version can modify the parameter." << endl;
}

int main()
{
	MemoryBlock block;
	f(block);
	f(MemoryBlock());

	return 0;
}

main 함수의 처음 f() 호출은 const MemoryBlock&을 전달받는 함수가 실행되고,

두 번째 f()의 호출은 MemoryBlock&&을 전달받는 함수가 실행됩니다.

그래서 결과는 다음과 같이 출력이 됩니다.

오버로딩을 통해서 넘어오는 파라미터에 맞는 함수를 수행하게 됩니다.

2) Named RValue Reference를 LValue로, Unnamed RValue Reference를 RValue로 처리한다.

RValue가 RValue Reference를 통해서 참조되면 해당 값은 LValue로 처리된다는 의미입니다.

RValue라고 해도 참조가 시작되면 코드의 다른 곳에서도 참조가 가능하기 때문에 LValue처럼 다뤄져야 합니다.

#include <iostream>
using namespace std;

// A class that contains a memory resource.
class MemoryBlock
{
};

void g(const MemoryBlock&)
{
	cout << "In g(const MemoryBlock&)." << endl;
}

void g(MemoryBlock&&)
{
	cout << "In g(MemoryBlock&&)." << endl;
}

MemoryBlock&& f(MemoryBlock&& block)
{
	g(block);

	return std::move(block);
}

int main()
{
	g(f(MemoryBlock()));

	return 0;
}

f() 함수에 RValue를 전달해줬으나, f() 함수 내의 block은 내부에서 LValue처럼 다뤄지기 때문에

g(const MemoryBlock&)이 실행됩니다.

그리고 그 이후에 다시 std::move로 리턴하게 되면 RValue로 처리되면서 g(MemoryBlock&&)이 실행됩니다.

결과는 다음과 같이 출력됩니다.

3) LValue를 RValue Reference로 캐스팅 가능하다.

위에서 표현된 내용입니다.

std::move()를 이용해서 LValue로 다뤄지는 block을 RValue Reference로 만들었습니다.

static_cast를 사용해서도 동일한 결과를 얻을 수 있습니다.

4) 함수 템플릿은 템플릿 인자 타입을 추론하고, Reference Collapsing Rules 를 사용한다.

함수 템플릿은 구체화 시에 템플릿 인자의 타입을 추론한 이후에 Reference Collapsing Rules을 사용합니다.

이것은 Perfect Fowarding의 구현이 가능하게 되는 중요한 내용입니다.

다음 예제를 보겠습니다.

#include <iostream>
#include <string>
using namespace std;

template<typename T> struct S;

// The following structures specialize S by
// lvalue reference (T&), const lvalue reference (const T&),
// rvalue reference (T&&), and const rvalue reference (const T&&).
// Each structure provides a print method that prints the type of
// the structure and its parameter.

template<typename T> struct S < T& > {
	static void print(T& t)
	{
		cout << "print<T&>: " << t << endl;
	}
};

template<typename T> struct S < const T& > {
	static void print(const T& t)
	{
		cout << "print<const T&>: " << t << endl;
	}
};

template<typename T> struct S < T&& > {
	static void print(T&& t)
	{
		cout << "print<T&&>: " << t << endl;
	}
};

template<typename T> struct S < const T&& > {
	static void print(const T&& t)
	{
		cout << "print<const T&&>: " << t << endl;
	}
};

// This function forwards its parameter to a specialized
// version of the S type.
template <typename T> void print_type_and_value(T&& t)
{
	S<T&&>::print(std::forward<T>(t));
}

// This function returns the constant string "fourth".
const string fourth() { return string("fourth"); }

int main()
{
	// The following call resolves to:
	// print_type_and_value<string&>(string& && t)
	// Which collapses to:
	// print_type_and_value<string&>(string& t)
	string s1("first");
	print_type_and_value(s1);

	// The following call resolves to:
	// print_type_and_value<const string&>(const string& && t)
	// Which collapses to:
	// print_type_and_value<const string&>(const string& t)
	const string s2("second");
	print_type_and_value(s2);

	// The following call resolves to:
	// print_type_and_value<string&&>(string&& t)
	print_type_and_value(string("third"));

	// The following call resolves to:
	// print_type_and_value<const string&&>(const string&& t)
	print_type_and_value(fourth());
}

main(wmain) 함수의 첫 번째 예제처럼 RValue Reference 타입을 전달 받는 함수 템플릿에

LValue인 s1을 전달한 경우T가 string& 타입으로 추론되고 세 번째 예제처럼 RValue(string("third"))를 전달하면

타입이 string으로 추론됩니다.

그러면 각 print_type_and_value 함수 호출 위의 주석에 적힌 것과 같은 형태로 함수가 결정이 됩니다.

주석 두 번째 라인인 // print_type_and_value<string&>(string& && t) 부분이 추론된 함수입니다.

보시면 첫 번째 호출같은 경우 은 string& && t 타입을 받는 이상한 형태가 됩니다.

이 때, Reference Collapsing Rules가 적용됩니다.

// Which collapses to: 이하의 주석이 Reference Collapsing Rules가 적용된 함수입니다.

결론적으로 Reference Collapsing Rules는 다음과 같은 규칙을 갖습니다.

Expanded Type

 Collapsed Type

 T& &

 T&

 T&  &&

 T&

 T&& &

 T&

 T&& &&

 T&&

RValue Reference를 전달받는 함수 템플릿은 RValue가 전달될 때는 T&&타입으로,

LValue가 전달될 때는 T& 타입으로 자연스럽게 Collapsing 되어 Perfect Fowarding이 가능한 것입니다.

이것으로 RValue Reference에 대한 모든 설명이 끝났습니다.

조금 어려울 수 있는 내용이지만, 성능적 이점과 코드 상의 이점을 동시에 가질 수 있기 때문에

적극적으로 활용하는 것을 권장합니다.


반응형

'Programming > CPP11&14' 카테고리의 다른 글

[C++11] cbegin()과 cend(), crbegin()과 crend()  (0) 2014.12.24
[C++11] final과 override  (0) 2014.12.21
[C++11] RValue Reference(3)  (0) 2014.12.18
[C++11] RValue Reference(2)  (0) 2014.12.17
[C++11] RValue Reference(1)  (0) 2014.12.14