Rvalue 참조 선언자: &&

rvalue 식에 대한 참조를 보유합니다.

구문

rvalue-reference-type-id:
type-specifier-seq&&attribute-specifier-seqoptptr-abstract-declaratoropt

설명

rvalue 참조를 사용하면 lvalue와 rvalue를 구별할 수 있습니다. Lvalue 참조 및 rvalue 참조는 구문적으로 의미상 유사하지만 약간 다른 규칙을 따릅니다. lvalues 및 rvalues에 대한 자세한 내용은 Lvalues 및 Rvalues를 참조 하세요. lvalue 참조에 대한 자세한 내용은 Lvalue 참조 선언자:&를 참조하세요.

다음 섹션에서는 rvalue 참조가 이동 의미 체계 및 완벽한 전달 구현 지원하는 방법을 설명합니다.

이동 의미 체계

Rvalue 참조는 애플리케이션의 성능을 크게 높일 수 있는 이동 의미 체계의 구현을 지원합니다. 의미 체계 이동을 사용하여 한 개체에서 다른 개체로 리소스(예: 동적으로 할당된 메모리)를 전송하는 코드를 작성할 수 있습니다. 이동 의미 체계는 임시 개체(프로그램의 다른 곳에서는 참조할 수 없는 개체)에서 리소스를 전송할 수 있기 때문에 작동합니다.

이동 의미 체계를 구현하려면 일반적으로 이동 생성자 및 필요에 따라 클래스에 이동 할당 연산자(operator=)를 제공합니다. 이렇게 하면 해당 소스가 rvalue인 복사 및 할당 작업에서 자동으로 의미 체계 이동을 활용합니다. 기본 복사 생성자와 달리 컴파일러는 기본 이동 생성자를 제공하지 않습니다. 이동 생성자를 작성하고 사용하는 방법에 대한 자세한 내용은 이동 생성자 및 이동 할당 연산자를 참조 하세요.

일반 함수 및 연산자를 오버로드하여 의미 체계 이동을 활용할 수도 있습니다. Visual Studio 2010에서는 C++ 표준 라이브러리로 이동 의미 체계를 도입했습니다. 예를 들어 클래스는 string 이동 의미 체계를 사용하는 작업을 구현합니다. 여러 문자열을 연결하고 결과를 출력하는 다음 예제를 살펴보십시오.

// string_concatenation.cpp
// compile with: /EHsc
#include <iostream>
#include <string>
using namespace std;

int main()
{
   string s = string("h") + "e" + "ll" + "o";
   cout << s << endl;
}

Visual Studio 2010 이전에는 각 호출에서 operator+ 새 임시 string 개체(rvalue)를 할당하고 반환합니다. operator+ 원본 문자열이 lvalue인지 rvalue인지 알 수 없으므로 한 문자열을 다른 문자열에 추가할 수 없습니다. 원본 문자열이 모두 lvalue인 경우 프로그램의 다른 위치에서 참조될 수 있으므로 수정해서는 안 됩니다. 프로그램의 다른 곳에서는 참조할 수 없는 rvalue 참조를 사용하여 rvalue를 사용하도록 수정 operator+ 할 수 있습니다. 이 변경으로 operator+ 이제 한 문자열을 다른 문자열에 추가할 수 있습니다. 이 변경으로 클래스에서 수행해야 하는 string 동적 메모리 할당 수가 크게 줄어듭니다. 클래스에 대한 자세한 내용은 클래스를 string 참조 basic_string 하세요.

또한 이동 의미 체계는 컴파일러가 RVO(반환 값 최적화) 또는 NRVO(명명된 반환 값 최적화)를 사용할 수 없는 경우에도 도움이 됩니다. 이러한 경우에 컴파일러는 형식에 정의된 이동 생성자를 호출합니다.

의미 체계 이동에 대해 자세히 이해하려면 요소를 vector 개체에 삽입하는 예를 참조하십시오. 개체의 vector 용량을 초과하는 vector 경우 개체는 해당 요소에 충분한 메모리를 다시 할당한 다음 각 요소를 다른 메모리 위치에 복사하여 삽입된 요소의 공간을 확보해야 합니다. 삽입 작업에서 요소를 복사할 때 먼저 새 요소를 만듭니다. 그런 다음 복사 생성자를 호출하여 이전 요소에서 새 요소로 데이터를 복사합니다. 마지막으로 이전 요소를 삭제합니다. 의미 체계를 이동하면 비용이 많이 드는 메모리 할당 및 복사 작업을 수행하지 않고도 개체를 직접 이동할 수 있습니다.

vector 예에서 의미 체계 이동을 활용하기 위해 한 개체에서 다른 개체로 데이터를 이동하는 이동 생성자를 작성할 수 있습니다.

Visual Studio 2010에서 C++ 표준 라이브러리로 이동 의미 체계를 도입하는 방법에 대한 자세한 내용은 C++ 표준 라이브러리를 참조 하세요.

완벽한 전달

완벽한 전달은 오버로드된 함수의 필요성을 줄이고 전달 문제를 방지하는 데 도움이 됩니다. 전달 문제는 참조를 매개 변수로 사용하는 제네릭 함수를 작성할 때 발생할 수 있습니다. 이러한 매개 변수를 다른 함수로 전달하거나 전달하는 경우(예: 형식 const T&의 매개 변수를 사용하는 경우) 호출된 함수는 해당 매개 변수의 값을 수정할 수 없습니다. 제네릭 함수가 형식 T&의 매개 변수를 사용하는 경우 rvalue(예: 임시 개체 또는 정수 리터럴)를 사용하여 함수를 호출할 수 없습니다.

일반적으로 이 문제를 해결하려면 각 매개 변수에 대해 T&const T&를 둘 다 사용하는 제네릭 함수의 오버로드된 버전을 제공해야 합니다. 따라서 오버로드된 함수의 수가 매개 변수의 수와 함께 급격하게 증가합니다. Rvalue 참조를 사용하면 임의의 인수를 허용하는 함수의 한 버전을 작성할 수 있습니다. 그런 다음 해당 함수는 다른 함수가 직접 호출된 것처럼 다른 함수로 전달할 수 있습니다.

W, X, YZ의 4가지 형식을 선언하는 다음 예제를 살펴보십시오. 각 형식에 대한 생성자는 매개 변수로 다른 조합 및 const 비-const lvalue 참조를 사용합니다.

struct W
{
   W(int&, int&) {}
};

struct X
{
   X(const int&, int&) {}
};

struct Y
{
   Y(int&, const int&) {}
};

struct Z
{
   Z(const int&, const int&) {}
};

개체를 생성하는 제네릭 함수를 작성한다고 가정합니다. 다음 예제에서는 이 함수를 작성하는 한 가지 방법을 보여 줍니다.

template <typename T, typename A1, typename A2>
T* factory(A1& a1, A2& a2)
{
   return new T(a1, a2);
}

다음 예제에서는 factory 함수에 대한 유효한 호출을 보여 줍니다.

int a = 4, b = 5;
W* pw = factory<W>(a, b);

그러나 다음 예제에서는 함수에 대한 유효한 호출을 factory 포함하지 않습니다. factory 매개 변수로 수정할 수 있는 lvalue 참조를 사용하지만 rvalues를 사용하여 호출되기 때문입니다.

Z* pz = factory<Z>(2, 2);

일반적으로 이 문제를 해결하려면 factoryA& 매개 변수의 모든 조합에 대해 const A& 함수의 오버로드된 버전을 만들어야 합니다. rvalue 참조를 사용하면 다음 예제와 같이 factory 함수의 버전을 작성할 수 있습니다.

template <typename T, typename A1, typename A2>
T* factory(A1&& a1, A2&& a2)
{
   return new T(std::forward<A1>(a1), std::forward<A2>(a2));
}

이 예제에서는 함수에 대한 매개 변수로 rvalue 참조를 factory 사용합니다. 이 함수의 std::forward 목적은 팩터리 함수의 매개 변수를 템플릿 클래스의 생성자로 전달하는 것입니다.

다음 예제에서는 main, factory, WX 클래스의 인스턴스를 만들기 위해 수정된 Y 함수를 사용하는 Z 함수를 보여 줍니다. 수정된 factory 함수는 매개 변수(lvalue 또는 rvalue)를 적절한 클래스 생성자에 전달합니다.

int main()
{
   int a = 4, b = 5;
   W* pw = factory<W>(a, b);
   X* px = factory<X>(2, b);
   Y* py = factory<Y>(a, 2);
   Z* pz = factory<Z>(2, 2);

   delete pw;
   delete px;
   delete py;
   delete pz;
}

rvalue 참조의 속성

함수를 오버로드하여 lvalue 참조 및 rvalue 참조를 수행할 수 있습니다.

lvalue 참조 또는 rvalue 참조를 사용하도록 const 함수를 오버로드하면 수정할 수 없는 개체(lvalues)와 수정 가능한 임시 값(rvalues)을 구분하는 코드를 작성할 수 있습니다. 개체가 로 표시되지 const않는 한 rvalue 참조를 사용하는 함수에 개체를 전달할 수 있습니다. 다음 예제에서는 lvalue 참조 및 rvalue 참조를 사용하도록 오버로드되는 f 함수를 보여 줍니다. main 함수는 lvalue와 rvalue를 둘 다 사용하여 f를 호출합니다.

// reference-overload.cpp
// Compile with: /EHsc
#include <iostream>
using namespace std;

// A class that contains a memory resource.
class MemoryBlock
{
   // TODO: Add resources for the class here.
};

void f(const MemoryBlock&)
{
   cout << "In f(const MemoryBlock&). This version can't 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());
}

이 예제는 다음과 같은 출력을 생성합니다.

In f(const MemoryBlock&). This version can't modify the parameter.
In f(MemoryBlock&&). This version can modify the parameter.

이 예제에서 첫 번째 f 호출은 지역 변수(lvalue)를 인수로 전달하고, 두 번째 f 호출은 임시 개체를 인수로 전달합니다. 임시 개체는 프로그램의 다른 위치에서 참조할 수 없으므로 호출은 rvalue 참조를 사용하는 오버로드된 버전에 바인딩됩니다. 이 버전은 f 개체를 자유롭게 수정할 수 있습니다.

컴파일러는 명명된 rvalue 참조를 lvalue로 처리하고 명명되지 않은 rvalue 참조를 rvalue로 처리합니다.

rvalue 참조를 매개 변수로 사용하는 함수는 매개 변수를 함수 본문의 lvalue로 처리합니다. 컴파일러는 명명된 rvalue 참조를 lvalue로 처리합니다. 프로그램의 여러 부분에서 명명된 개체를 참조할 수 있기 때문입니다. 프로그램의 여러 부분이 해당 개체에서 리소스를 수정하거나 제거하도록 허용하는 것은 위험합니다. 예를 들어 프로그램의 여러 부분이 동일한 개체에서 리소스를 전송하려고 하면 첫 번째 전송만 성공합니다.

다음 예제에서는 lvalue 참조 및 rvalue 참조를 사용하도록 오버로드되는 g 함수를 보여 줍니다. f 함수는 rvalue 참조(명명된 rvalue 참조)를 매개 변수로 사용하고 rvalue 참조(명명되지 않은 rvalue 참조)를 반환합니다. g에서 f를 호출할 때 g 본문에서 매개 변수를 lvalue로 처리하기 때문에 오버로드 확인은 lvalue 참조를 사용하는 f의 버전을 선택합니다. g에서 main를 호출할 때 g에서 rvalue 참조를 반환하기 때문에 오버로드 확인은 rvalue 참조를 사용하는 f의 버전을 선택합니다.

// named-reference.cpp
// Compile with: /EHsc
#include <iostream>
using namespace std;

// A class that contains a memory resource.
class MemoryBlock
{
   // TODO: Add resources for the class here.
};

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 move(block);
}

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

이 예제는 다음과 같은 출력을 생성합니다.

In g(const MemoryBlock&).
In g(MemoryBlock&&).

이 예제에서 함수는 main rvalue를 .에 f전달합니다. f의 본문에서는 명명된 매개 변수를 lvalue로 처리합니다. f에서 g로의 호출은 매개 변수를 lvalue 참조(g의 첫 번째 오버로드된 버전)에 바인딩합니다.

  • lvalue를 rvalue 참조로 캐스팅할 수 있습니다.

C++ 표준 라이브러리 std::move 함수를 사용하면 개체를 해당 개체에 대한 rvalue 참조로 변환할 수 있습니다. 다음 예제와 static_cast 같이 키워드(keyword) 사용하여 lvalue를 rvalue 참조로 캐스팅할 수도 있습니다.

// cast-reference.cpp
// Compile with: /EHsc
#include <iostream>
using namespace std;

// A class that contains a memory resource.
class MemoryBlock
{
   // TODO: Add resources for the class here.
};

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

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

int main()
{
   MemoryBlock block;
   g(block);
   g(static_cast<MemoryBlock&&>(block));
}

이 예제는 다음과 같은 출력을 생성합니다.

In g(const MemoryBlock&).
In g(MemoryBlock&&).

함수 템플릿은 템플릿 인수 형식을 추론한 다음 참조 축소 규칙을 사용합니다.

해당 매개 변수를 다른 함수로 전달(또는 전달)하는 함수 템플릿은 일반적인 패턴입니다. rvalue 참조를 사용하는 함수 템플릿에서 템플릿 형식 추론이 작동하는 방식을 이해하는 것이 중요합니다.

함수 인수가 rvalue이면 컴파일러는 인수를 rvalue 참조로 추론합니다. 예를 들어 형식의 개체에 대한 rvalue 참조를 해당 X 매개 변수로 사용하는 T&& 함수 템플릿에 전달한다고 가정합니다. 템플릿 인수 추론은 TX매개 변수에 형식 X&&이 있습니다. 함수 인수가 lvalue 또는 const lvalue이면 컴파일러는 해당 형식을 해당 형식의 lvalue 참조 또는 const lvalue 참조로 추론합니다.

다음 예제에서는 하나의 구조체 템플릿을 선언한 다음 다양한 참조 형식에 대해 특수화합니다. print_type_and_value 함수는 rvalue 참조를 매개 변수로 사용하고, 이를 S::print 메서드의 적절한 특수화된 버전에 전달합니다. main 함수에서는 S::print 메서드를 호출하는 다양한 방법을 보여 줍니다.

// template-type-deduction.cpp
// Compile with: /EHsc
#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());
}

이 예제는 다음과 같은 출력을 생성합니다.

print<T&>: first
print<const T&>: second
print<T&&>: third
print<const T&&>: fourth

함수에 대한 각 호출을 print_type_and_value 해결하기 위해 컴파일러는 먼저 템플릿 인수 추론을 수행합니다. 그런 다음 컴파일러는 매개 변수 형식을 추론된 템플릿 인수로 대체할 때 참조 축소 규칙을 적용합니다. 예를 들어 지역 변수 s1print_type_and_value 함수에 전달하면 컴파일러에서 다음과 같은 함수 시그니처를 생성합니다.

print_type_and_value<string&>(string& && t)

컴파일러는 참조 축소 규칙을 사용하여 서명을 줄입니다.

print_type_and_value<string&>(string& t)

print_type_and_value 함수 버전은 매개 변수를 S::print 메서드의 올바른 특수화된 버전에 전달합니다.

다음 표에는 템플릿 인수 형식 추론에 대한 참조 축소 규칙이 요약되어 있습니다.

확장된 형식 축소된 형식
T& & T&
T& && T&
T&& & T&
T&& && T&&

템플릿 인수 추론은 완벽한 전달을 구현하는 중요한 요소입니다. 완벽한 전달 섹션에서는 완벽한 전달에 대해 자세히 설명합니다.

요약

rvalue 참조는 rvalue와 lvalue를 구별합니다. 애플리케이션의 성능을 향상시키기 위해 불필요한 메모리 할당 및 복사 작업이 필요하지 않도록 할 수 있습니다. 또한 임의의 인수를 허용하는 함수를 작성할 수 있습니다. 해당 함수는 다른 함수가 직접 호출된 것처럼 다른 함수로 전달할 수 있습니다.

참고 항목

단항 연산자가 있는 식
Lvalue 참조 선언자: &
Lvalues 및 rvalues
생성자 이동 및 할당 연산자 이동(C++)
C++ 표준 라이브러리