나는 C ++ 11의 새로운 기능 중 일부를 살펴 보았으며 내가 주목 한 것은 이중 앰퍼샌드와 같은 변수를 선언하는 것 T&& var
입니다.
처음에,이 짐승은 무엇입니까? Google에서 이와 같은 구두점을 검색 할 수 있기를 바랍니다.
정확히 무엇을 의미합니까?
언뜻보기에는 이중 참조 인 것처럼 보이지만 (C 스타일 이중 포인터와 같은 T** var
) 유스 케이스를 생각하는 데 어려움을 겪고 있습니다.
답변
rvalue 참조 (표준 제안서)를 선언합니다 .
다음은 rvalue reference에 대한 소개 입니다.
다음은 Microsoft의 표준 라이브러리 개발자 중 한 명이 제공하는 rvalue 참조에 대한 환상적인 심도 입니다 .
주의 : MSDN의 링크 된 기사 ( “Rvalue 참조 : VC10의 C ++ 0x 기능, 2 부”)는 Rvalue 참조에 대한 매우 명확한 소개이지만, C ++ 11 초안에서 한때 사실이었던 Rvalue 참조에 대한 설명을 제공합니다. 표준이지만 마지막 것은 사실이 아닙니다! 특히, 그것은 여러 가지 점에서 rvalue 참조가 lvalue에 바인딩 될 수 있다고 말하는데, 이는 한 번은 사실이지만 변경되었습니다 (예 : int x; int && rrx = x; 더 이상 GCC에서 컴파일되지 않음) – drewbarbs
C ++ 03 참조 (현재 C ++ 11에서 lvalue 참조라고 함)의 가장 큰 차이점은 const가 아니어도 임시처럼 rvalue에 바인딩 할 수 있다는 것입니다. 따라서이 구문은 이제 합법적입니다.
T&& r = T();
rvalue 참조는 주로 다음을 제공합니다.
의미를 이동합니다 . 일반적인 const-lvalue 참조 대신 rvalue 참조를 사용하는 이동 생성자 및 이동 지정 연산자를 정의 할 수 있습니다. 이동은 소스를 변경하지 않아도된다는 점을 제외하고는 복사와 같은 기능을합니다. 실제로는 일반적으로 소스를 수정하여 더 이상 이동 된 자원을 소유하지 않도록합니다. 이는 특히 표준 라이브러리 구현에서 불필요한 사본을 제거하는 데 좋습니다.
예를 들어, 복사 생성자는 다음과 같습니다.
foo(foo const& other)
{
this->length = other.length;
this->ptr = new int[other.length];
copy(other.ptr, other.ptr + other.length, this->ptr);
}
이 생성자가 임시로 전달 된 경우 임시가 소멸 될 것이므로 사본이 필요하지 않습니다. 임시로 이미 할당 된 리소스를 사용하지 않는 이유는 무엇입니까? C ++ 03에서는 임시로 전달되었다고 판단 할 수 없으므로 복사를 막을 방법이 없습니다. C ++ 11에서는 이동 생성자를 오버로드 할 수 있습니다.
foo(foo&& other)
{
this->length = other.length;
this->ptr = other.ptr;
other.length = 0;
other.ptr = nullptr;
}
여기서 큰 차이점을 알 수 있습니다. 이동 생성자는 실제로 인수를 수정합니다. 이렇게하면 임시를 구성중인 오브젝트로 효과적으로 “이동”하여 불필요한 사본을 제거 할 수 있습니다.
이동 생성자는 std::move
함수를 사용하여 rvalue 참조로 명시 적으로 변환되는 임시 및 비 const lvalue 참조에 사용 됩니다 (변환 만 수행함). 다음 코드는 f1
및에 대한 이동 생성자를 호출합니다 f2
.
foo f1((foo())); // Move a temporary into f1; temporary becomes "empty"
foo f2 = std::move(f1); // Move f1 into f2; f1 is now "empty"
완벽한 전달 . rvalue 참조를 통해 템플릿 함수에 대한 인수를 올바르게 전달할 수 있습니다. 이 팩토리 기능을 예로 들어 보겠습니다.
template <typename T, typename A1>
std::unique_ptr<T> factory(A1& a1)
{
return std::unique_ptr<T>(new T(a1));
}
우리가 호출 factory<foo>(5)
하면, 인수는로 추론됩니다 int&
. 이것은 foo
생성자가을 가져 도 리터럴 5에 바인딩되지 않습니다 int
. 글쎄, 대신 대신 사용할 수 A1 const&
있지만, foo
const가 아닌 참조로 생성자 인수 를 사용 하면 어떻게 됩니까? 진정으로 일반적인 팩토리 기능을 만들려면 팩토리를 켜고 켜야 A1&
합니다 A1 const&
. 팩토리가 1 개의 매개 변수 유형을 사용하는 경우에는 문제가 없지만 추가 매개 변수 유형마다 필요한 과부하 세트에 2를 곱하면 매우 빠르게 유지 관리 할 수 없습니다.
rvalue 참조는 표준 라이브러리가 std::forward
lvalue / rvalue 참조를 올바르게 전달할 수 있는 함수 를 정의 할 수있게하여이 문제를 해결합니다 . std::forward
작동 방식 에 대한 자세한 내용 은 이 훌륭한 답변을 참조하십시오 .
이를 통해 다음과 같이 팩토리 기능을 정의 할 수 있습니다.
template <typename T, typename A1>
std::unique_ptr<T> factory(A1&& a1)
{
return std::unique_ptr<T>(new T(std::forward<A1>(a1)));
}
이제 인수의 rvalue / lvalue-ness가의 T
생성자에 전달 될 때 유지됩니다 . 즉, 팩토리가 rvalue로 호출되면 T
의 생성자는 rvalue로 호출됩니다. lvalue로 factory를 호출하면 T
의 생성자가 lvalue로 호출됩니다. 개선 된 팩토리 기능은 하나의 특수 규칙으로 인해 작동합니다.
함수 파라미터 입력 양식의 경우 템플릿 파라미터 및 함수 인자는 타입의 좌변 인 , 유형 템플릿 인자 추론에 사용된다.
T&&
T
A
A&
따라서 팩토리를 다음과 같이 사용할 수 있습니다.
auto p1 = factory<foo>(foo()); // calls foo(foo&&)
auto p2 = factory<foo>(*p1); // calls foo(foo const&)
중요한 rvalue 참조 속성 :
- 과부하 해결의 경우, lvalue는 lvalue 참조에 대한 바인딩을 선호하고 rvalue는 rvalue 참조에 대한 바인딩을 선호합니다 . 따라서 임시 직원이 복사 생성자 / 할당 연산자보다 이동 생성자 / 이동 할당 연산자를 호출하는 것을 선호하는 이유는 무엇입니까?
- rvalue 참조는 암시 적으로 rvalue 및 암시 적 변환의 결과 인 임시에 바인딩됩니다 . 즉
float f = 0f; int&& i = f;
float는 암시 적으로 int로 변환 가능하기 때문에 잘 구성됩니다. 참조는 변환의 결과 인 임시에 대한 것입니다. - 명명 된 rvalue 참조는 lvalue입니다. 명명되지 않은 rvalue 참조는 rvalue입니다.
std::move
전화가 왜 필요한지 이해하는 것이 중요합니다 .foo&& r = foo(); foo f = std::move(r);
답변
rvalue 참조를 나타냅니다. Rvalue 참조는 달리 명시 적으로 생성되지 않는 한 임시 객체에만 바인드됩니다. 특정 상황에서 객체를 훨씬 더 효율적으로 만들고 완벽한 전달이라고하는 기능을 제공하여 템플릿 코드를 크게 단순화하는 데 사용됩니다.
C ++ 03에서는 변경할 수없는 lvalue와 rvalue의 사본을 구별 할 수 없습니다.
std::string s;
std::string another(s); // calls std::string(const std::string&);
std::string more(std::string(s)); // calls std::string(const std::string&);
C ++ 0x에서는 그렇지 않습니다.
std::string s;
std::string another(s); // calls std::string(const std::string&);
std::string more(std::string(s)); // calls std::string(std::string&&);
이러한 생성자 뒤에 구현을 고려하십시오. 첫 번째 경우, 문자열은 새로운 힙 할당을 포함하는 값 의미론을 유지하기 위해 복사를 수행해야합니다. 그러나 두 번째 경우에는 생성자로 전달 된 객체가 즉시 폐기 될 예정이므로 그대로 유지하지 않아도됩니다. 이 시나리오에서는 내부 포인터를 효과적으로 교체하고 복사를 전혀 수행 할 수 없으며 이는 훨씬 더 효율적입니다. 이동 의미론은 내부 참조 자원의 비싸거나 금지 된 복사가있는 모든 클래스에 혜택을줍니다. 의 경우를 생각해 std::unique_ptr
지금 우리의 클래스는 임시직이 아닌 임시직을 구별 할 수있는, 우리가 이동 의미가 제대로 그래서이 있음을 작동 할 수 있습니다 – unique_ptr
어떤 의미, 복사 할 수 없습니다 있지만 이동할 수 있습니다std::unique_ptr
C ++ 03은 표준 컨테이너에 합법적으로 저장하고 정렬 std::auto_ptr
할 수 있습니다.
이제 rvalue 참조의 다른 사용-완벽한 전달을 고려합니다. 참조를 참조에 바인딩하는 문제를 고려하십시오.
std::string s;
std::string& ref = s;
(std::string&)& anotherref = ref; // usually expressed via template
C ++ 03이 이것에 대해 말한 것을 기억할 수는 없지만 C ++ 0x에서는 rvalue 참조를 처리 할 때 결과 유형이 중요합니다. 유형 T에 대한 rvalue 참조 (여기서 T는 참조 유형 임)는 유형 T의 참조가됩니다.
(std::string&)&& ref // ref is std::string&
(const std::string&)&& ref // ref is const std::string&
(std::string&&)&& ref // ref is std::string&&
(const std::string&&)&& ref // ref is const std::string&&
가장 간단한 템플릿 기능인 min과 max를 고려하십시오. C ++ 03에서는 const와 non-const의 네 가지 조합을 모두 수동으로 오버로드해야합니다. C ++ 0x에서는 단지 하나의 과부하입니다. 다양한 템플릿과 결합하여 완벽한 전달이 가능합니다.
template<typename A, typename B> auto min(A&& aref, B&& bref) {
// for example, if you pass a const std::string& as first argument,
// then A becomes const std::string& and by extension, aref becomes
// const std::string&, completely maintaining it's type information.
if (std::forward<A>(aref) < std::forward<B>(bref))
return std::forward<A>(aref);
else
return std::forward<B>(bref);
}
반환 유형 공제를 중단했습니다. 왜냐하면 그것이 어떻게 수행되었는지를 기억할 수는 없지만 최소값은 lvalue, rvalue, const lvalue의 조합을 허용 할 수 있습니다.
답변
T&&
유형 공제와 함께 사용될 때 (완전한 전달과 같은) 용어 는 구어체 적으로 전달 참조라고 합니다. “Universal reference”라는 용어 는이 기사에서 Scott Meyers 가 만들었지 만 나중에 변경되었습니다.
r- 값 또는 l- 값일 수 있기 때문입니다.
예를 들면 다음과 같습니다.
// template
template<class T> foo(T&& t) { ... }
// auto
auto&& t = ...;
// typedef
typedef ... T;
T&& t = ...;
// decltype
decltype(...)&& t = ...;
: 더 논의에 대한 대답에서 찾을 수 있습니다 보편적 참조 구문
답변
rvalue 참조는 몇 가지 예외를 제외하고 일반 참조 X &와 매우 유사하게 작동하는 유형입니다. 가장 중요한 것은 기능 과부하 해결에있어 lvalue는 구식 lvalue 참조를 선호하는 반면 rvalue는 새로운 rvalue 참조를 선호한다는 것입니다.
void foo(X& x); // lvalue reference overload
void foo(X&& x); // rvalue reference overload
X x;
X foobar();
foo(x); // argument is lvalue: calls foo(X&)
foo(foobar()); // argument is rvalue: calls foo(X&&)
rvalue는 무엇입니까? lvalue가 아닌 모든 것. lvalue는 메모리 위치를 나타내는 표현식이며 & 연산자를 통해 해당 메모리 위치의 주소를 가져올 수 있습니다.
예제를 통해 rvalue가 무엇을 달성하는지 이해하는 것이 가장 쉽습니다.
#include <cstring>
class Sample {
int *ptr; // large block of memory
int size;
public:
Sample(int sz=0) : ptr{sz != 0 ? new int[sz] : nullptr}, size{sz}
{
if (ptr != nullptr) memset(ptr, 0, sz);
}
// copy constructor that takes lvalue
Sample(const Sample& s) : ptr{s.size != 0 ? new int[s.size] :\
nullptr}, size{s.size}
{
if (ptr != nullptr) memcpy(ptr, s.ptr, s.size);
std::cout << "copy constructor called on lvalue\n";
}
// move constructor that take rvalue
Sample(Sample&& s)
{ // steal s's resources
ptr = s.ptr;
size = s.size;
s.ptr = nullptr; // destructive write
s.size = 0;
cout << "Move constructor called on rvalue." << std::endl;
}
// normal copy assignment operator taking lvalue
Sample& operator=(const Sample& s)
{
if(this != &s) {
delete [] ptr; // free current pointer
size = s.size;
if (size != 0) {
ptr = new int[s.size];
memcpy(ptr, s.ptr, s.size);
} else
ptr = nullptr;
}
cout << "Copy Assignment called on lvalue." << std::endl;
return *this;
}
// overloaded move assignment operator taking rvalue
Sample& operator=(Sample&& lhs)
{
if(this != &s) {
delete [] ptr; //don't let ptr be orphaned
ptr = lhs.ptr; //but now "steal" lhs, don't clone it.
size = lhs.size;
lhs.ptr = nullptr; // lhs's new "stolen" state
lhs.size = 0;
}
cout << "Move Assignment called on rvalue" << std::endl;
return *this;
}
//...snip
};
생성자와 할당 연산자는 rvalue 참조를 사용하는 버전으로 오버로드되었습니다. Rvalue 참조를 사용하면 “lvalue 또는 rvalue에서 호출되고 있습니까?”라는 조건에서 함수가 컴파일 시간 (과부하 해결을 통해)으로 분기 될 수 있습니다. 이를 통해 리소스를 복사하는 대신보다 효율적인 생성자 및 할당 연산자를 생성 할 수있었습니다.
이동 생성자 또는 이동 할당 연산자를 호출해야하는지 여부를 선택하는 컴파일러는 컴파일 할 때 자동으로 분기합니다 (lvalue 또는 rvalue에 대해 호출되는지 여부에 따라 다름).
요약 : rvalue 참조는 이동 의미론 (그리고 아래 기사 링크에서 설명한 완벽한 전달)을 허용합니다.
이해하기 쉬운 한 가지 예는 클래스 템플릿 std :: unique_ptr 입니다. unique_ptr은 기본 raw 포인터의 독점 소유권을 유지하므로 unique_ptr은 복사 할 수 없습니다. 그것은 그들의 독점 소유권의 불변을 위반할 것입니다. 따라서 복사 생성자가 없습니다. 그러나 이동 생성자가 있습니다.
template<class T> class unique_ptr {
//...snip
unique_ptr(unique_ptr&& __u) noexcept; // move constructor
};
std::unique_ptr<int[] pt1{new int[10]};
std::unique_ptr<int[]> ptr2{ptr1};// compile error: no copy ctor.
// So we must first cast ptr1 to an rvalue
std::unique_ptr<int[]> ptr2{std::move(ptr1)};
std::unique_ptr<int[]> TakeOwnershipAndAlter(std::unique_ptr<int[]> param,\
int size)
{
for (auto i = 0; i < size; ++i) {
param[i] += 10;
}
return param; // implicitly calls unique_ptr(unique_ptr&&)
}
// Now use function
unique_ptr<int[]> ptr{new int[10]};
// first cast ptr from lvalue to rvalue
unique_ptr<int[]> new_owner = TakeOwnershipAndAlter(\
static_cast<unique_ptr<int[]>&&>(ptr), 10);
cout << "output:\n";
for(auto i = 0; i< 10; ++i) {
cout << new_owner[i] << ", ";
}
output:
10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
static_cast<unique_ptr<int[]>&&>(ptr)
일반적으로 std :: move를 사용하여 수행됩니다.
// first cast ptr from lvalue to rvalue
unique_ptr<int[]> new_owner = TakeOwnershipAndAlter(std::move(ptr),0);
좋은 예가 많은 rvalues가 완벽한 전달을 허용하는 방법과 그 의미와 같은 모든 것을 설명하는 훌륭한 기사는 Thomas Becker의 C ++ Rvalue References Explained 입니다. 이 게시물은 그의 기사에 크게 의존했습니다.
짧은 소개는 Stroutrup의 Rvalue References 에 대한 간략한 소개 입니다. 알