"* this에 대한 rvalue 참조"란 무엇입니까?


238

clang의 C ++ 11 status page 에서 "* r에 대한 rvalue reference"라는 제안을 보았습니다 .

rvalue 참조에 대해 꽤 많이 읽고 이해했지만 이것에 대해 알지 못한다고 생각합니다. 또한 용어를 사용하여 웹에서 많은 리소스를 찾을 수 없습니다.

페이지에 제안서에 대한 링크가 있습니다 : N2439 (이동 의미론을 * this로 확장), 그러나 거기에서도 많은 예제를 얻지 못했습니다.

이 기능은 무엇입니까?

답변:


293

먼저, "* this의 한정자"는 "마케팅 명세서"일뿐입니다. *this변경되지 않는 유형은 이 게시물의 하단을 참조하십시오. 이 말로 이해하기가 훨씬 쉽습니다.

다음 코드 는 함수 의 "암시 적 객체 매개 변수"의 참조 한정자 를 기반으로 호출 할 함수를 선택합니다 .

// t.cpp
#include <iostream>

struct test{
  void f() &{ std::cout << "lvalue object\n"; }
  void f() &&{ std::cout << "rvalue object\n"; }
};

int main(){
  test t;
  t.f(); // lvalue
  test().f(); // rvalue
}

산출:

$ clang++ -std=c++0x -stdlib=libc++ -Wall -pedantic t.cpp
$ ./a.out
lvalue object
rvalue object

함수가 호출되는 객체가 rvalue (예 : 이름이없는 임시) 인 경우를 활용하기 위해 모든 것이 수행됩니다. 추가 코드로 다음 코드를 사용하십시오.

struct test2{
  std::unique_ptr<int[]> heavy_resource;

  test2()
    : heavy_resource(new int[500]) {}

  operator std::unique_ptr<int[]>() const&{
    // lvalue object, deep copy
    std::unique_ptr<int[]> p(new int[500]);
    for(int i=0; i < 500; ++i)
      p[i] = heavy_resource[i];

    return p;
  }

  operator std::unique_ptr<int[]>() &&{
    // rvalue object
    // we are garbage anyways, just move resource
    return std::move(heavy_resource);
  }
};

이것은 약간 생각할 수도 있지만 아이디어를 얻어야합니다.

당신이 결합 할 수 있습니다 이력서 - 예선 ( constvolatile)과 REF-예선 ( &&&).


참고 : 여기에 많은 표준 인용문과 과부하 해결 설명이 있습니다!

† 이것이 어떻게 작동하고 @Nicol Bolas의 대답이 적어도 부분적으로 잘못된 이유를 이해하려면 C ++ 표준을 약간 파헤쳐 야합니다. 그것에 관심이 있습니다).

어떤 함수가 호출 될지는 과부하 해결 이라는 프로세스에 의해 결정됩니다 . 이 과정은 상당히 복잡하므로 중요한 비트 만 건 드리게됩니다.

먼저 멤버 함수의 오버로드 해상도가 어떻게 작동하는지 확인하는 것이 중요합니다.

§13.3.1 [over.match.funcs]

p2 후보 함수 세트는 동일한 인수 목록에 대해 분석 할 멤버 함수와 비 멤버 함수를 모두 포함 할 수 있습니다. 따라서 이기종 세트 내에서 인수 및 매개 변수 목록을 비교할 수 있도록 구성원 함수에는 암시 적 오브젝트 매개 변수라고하는 추가 매개 변수가있는 것으로 간주됩니다.이 매개 변수는 구성원 함수가 호출 된 오브젝트를 나타냅니다 . [...]

유사하게, 적절한 경우, 컨텍스트는 조작 될 오브젝트를 표시하기 위해 내재 된 오브젝트 인수 를 포함하는 인수리스트를 구성 할 수 있습니다 .

왜 멤버 함수와 비 멤버 함수를 비교해야합니까? 연산자 오버로드가 그 이유입니다. 이걸 고려하세요:

struct foo{
  foo& operator<<(void*); // implementation unimportant
};

foo& operator<<(foo&, char const*); // implementation unimportant

다음과 같이 free 함수를 호출하고 싶습니까?

char const* s = "free foo!\n";
foo f;
f << s;

이것이 멤버 및 비 멤버 함수가 소위 오버로드 세트에 포함되는 이유입니다. 해상도를 덜 복잡하게하기 위해 표준 인용의 굵은 부분이 있습니다. 또한 이것은 우리에게 중요한 부분입니다 (동일한 조항).

p4 비 정적 멤버 함수의 경우 내재적 오브젝트 매개 변수의 유형은 다음과 같습니다.

  • "좌변에 참조 CV X 기능"은 선언하지 REF - 규정 하거나 함께 & REF - 규정

  • ref 한정자로 선언 된 함수의 경우 " cv에 대한 rvalue 참조 X"&&

여기서 X함수는 멤버 인 클래스이고 cv 는 멤버 함수 선언의 cv 규정입니다. [...]

p5 과부하 해결 중 [...] [t] 암시 적 객체 매개 변수 [...]는 해당 인수에 대한 변환이 다음 추가 규칙을 준수해야하므로 ID를 유지합니다.

  • 내재적 오브젝트 매개 변수에 대한 인수를 보유하기 위해 임시 오브젝트를 도입 할 수 없습니다. 과

  • 형식 일치를 위해 사용자 정의 변환을 적용 할 수 없습니다

[...]

(마지막 비트는 멤버 함수 (또는 연산자)가 호출되는 객체의 암시 적 변환을 기반으로 과부하 해결을 속일 수 없음을 의미합니다.)

이 게시물의 맨 위에있는 첫 번째 예를 살펴 보겠습니다. 위에서 언급 한 변환 후 오버로드 세트는 다음과 같습니다.

void f1(test&); // will only match lvalues, linked to 'void test::f() &'
void f2(test&&); // will only match rvalues, linked to 'void test::f() &&'

그런 다음 묵시적 객체 argument가 포함 된 인수 목록 이 오버로드 집합에 포함 된 모든 함수의 매개 변수 목록과 일치합니다. 이 경우 인수 목록에는 해당 객체 인수 만 포함됩니다. 어떻게 보이는지 봅시다 :

// first call to 'f' in 'main'
test t;
f1(t); // 't' (lvalue) can match 'test&' (lvalue reference)
       // kept in overload-set
f2(t); // 't' not an rvalue, can't match 'test&&' (rvalue reference)
       // taken out of overload-set

세트의 모든 과부하가 테스트 된 후에도 하나만 남아 있으면 과부하 해결에 성공하고 해당 변환 된 과부하에 연결된 함수가 호출됩니다. 'f'에 대한 두 번째 호출도 마찬가지입니다.

// second call to 'f' in 'main'
f1(test()); // 'test()' not an lvalue, can't match 'test&' (lvalue reference)
            // taken out of overload-set
f2(test()); // 'test()' (rvalue) can match 'test&&' (rvalue reference)
            // kept in overload-set

참고 그러나, 우리는 제공하지 않았다 REF-규정 즉, (와 같은 기능을 오버로드되지 않음) f1 를 rvalue을 (여전히 일치 §13.3.1) :

p5 [...] ref-qualifier 없이 선언 된 비 정적 멤버 함수의 경우 추가 규칙이 적용됩니다.

  • 내재적 오브젝트 매개 변수가 const-qualified 가 아니더라도 다른 모든 측면에서 인수가 내재적 오브젝트 매개 변수의 유형으로 변환 될 수있는 한 rvalue는 매개 변수에 바인드 될 수 있습니다.
struct test{
  void f() { std::cout << "lvalue or rvalue object\n"; }
};

int main(){
  test t;
  t.f(); // OK
  test().f(); // OK too
}

이제 @Nicol의 대답이 부분적으로 잘못된 이유는 무엇입니까? 그는 말한다 :

이 선언은의 유형을 변경합니다 *this.

그것은 항상 잘못된 것 *this입니다 .

§5.3.1 [expr.unary.op] p1

단항 *연산자는 간접적으로 수행합니다 . 적용되는 표현식은 객체 유형에 대한 포인터 또는 함수 유형에 대한 포인터 여야하며 결과는 표현식이 가리키는 객체 또는 함수를 참조 하는 lvalue 입니다.

§9.3.2 [class.this] p1

비 정적 (9.3) 멤버 함수의 본문에서 키워드 this는 값이 함수가 호출되는 객체의 주소 인 prvalue 표현식입니다. this클래스 멤버 함수의 유형은 X입니다 X*. [...]


"변환 후"섹션 바로 뒤에있는 paraneter 유형은 'test'대신 'foo'여야한다고 생각합니다.
ryaner dec

@ ryaner : 좋은 찾기 감사합니다. 매개 변수가 아니라 함수의 클래스 식별자가 잘못되었습니다. :)
Xeo

죄송합니다. 그 부분을 읽고 f가 foo에 포함되어 있다고 생각할 때 test라는 장난감 클래스를 잊어 버렸습니다.
ryaner

생성자로이 작업을 수행 할 수 있습니까 MyType(int a, double b) &&?
Germán Diago

2
"*이 유형은 절대 변하지 않습니다"r / l-value 자격에 따라 변경되지 않는다는 것이 조금 더 명확해야합니다. 그러나 const / non-const 사이에서 변경할 수 있습니다.
xaxxon

78

lvalue 참조 규정 자 양식에 대한 추가 사용 사례가 있습니다. C ++ 98에는 constrvalue 인 클래스 인스턴스에 멤버가 아닌 함수를 호출 할 수있는 언어가 있습니다 . 이것은 rvalueness의 개념에 반하는 모든 종류의 기묘함을 초래하고 내장 유형이 작동하는 방식에서 벗어납니다.

struct S {
  S& operator ++(); 
  S* operator &(); 
};
S() = S();      // rvalue as a left-hand-side of assignment!
S& foo = ++S(); // oops, dangling reference
&S();           // taking address of rvalue...

Lvalue 참조 한정자는 이러한 문제를 해결합니다.

struct S {
  S& operator ++() &;
  S* operator &() &;
  const S& operator =(const S&) &;
};

이제 연산자는 기본 제공 유형의 연산자처럼 작동하며 lvalue 만 허용합니다.


28

이름과 서명이 같은 클래스에 두 개의 함수가 있다고 가정 해 봅시다. 그러나 그중 하나가 선언됩니다 const.

void SomeFunc() const;
void SomeFunc();

클래스 인스턴스 const가이 아닌 경우 과부하 해결은 우선적으로 비 const 버전을 선택합니다. 인스턴스가 const인 경우 사용자는 const버전 만 호출 할 수 있습니다 . 그리고 this포인터는const 포인터이므로 인스턴스를 변경할 수 없습니다.

"r- 값 참조"는 다른 대안을 추가 할 수있게합니다.

void RValueFunc() &&;

이를 통해 사용자가 적절한 r- 값을 통해 호출하는 경우 에만 호출 할 수있는 함수를 가질 수 있습니다 . 따라서 이것이 유형이라면 Object:

Object foo;
foo.RValueFunc(); //error: no `RValueFunc` version exists that takes `this` as l-value.
Object().RValueFunc(); //calls the non-const, && version.

이런 식으로 r- 값을 통해 객체에 액세스하는지 여부에 따라 동작을 특수화 할 수 있습니다.

r- 값 참조 버전과 비 참조 버전간에 과부하를 허용하지 않습니다. 즉, 멤버 함수 이름이 있으면 모든 버전에서에 l / r- 값 한정자를 사용하거나 사용 this하지 않습니다. 당신은 이것을 할 수 없습니다 :

void SomeFunc();
void SomeFunc() &&;

이 작업을 수행해야합니다.

void SomeFunc() &;
void SomeFunc() &&;

이 선언은의 유형을 변경합니다 *this. 이는 &&버전이 모두 r- 값 참조로 멤버에 액세스 함을 의미합니다 . 따라서 물체 내에서 쉽게 이동할 수 있습니다. 제안서의 첫 번째 버전에 제시된 예는 다음과 같습니다 (참고 : C ++ 11의 최종 버전에서는 다음 사항이 정확하지 않을 수 있습니다. 초기 "r-value from this"제안서와 동일).

class X {
   std::vector<char> data_;
public:
   // ...
   std::vector<char> const & data() const & { return data_; }
   std::vector<char> && data() && { return data_; }
};

X f();

// ...
X x;
std::vector<char> a = x.data(); // copy
std::vector<char> b = f().data(); // move

2
나는 당신이 필요하다고 생각 std::move두 번째 버전, 비? 또한 rvalue 참조가 왜 반환됩니까?
Xeo

1
@ Xeo : 제안서에 예제가 있었기 때문입니다. 현재 버전에서 여전히 작동하는지 전혀 모르겠습니다. 그리고 r- 값 참조 리턴의 이유는 움직임을 캡처하는 사람에게 달려 있기 때문입니다. 실제로 값 대신 &&에 저장하려는 경우를 대비하여 아직 발생하지 않아야합니다.
Nicol Bolas

3
맞아, 나는 나의 두 번째 질문에 대한 이유를 생각했다. 그래도 rvalue가 임시 멤버를 가리키는 것이 그 임시 멤버의 수명을 연장 시키는가, 또는 그 멤버를 궁금해 하는가? 맹세 컨데 내가 얼마전에 그렇게 그것에 대해 질문을 봤다.
Xeo

1
@ Xeo : 그것은 사실이 아닙니다. 오버로드 해상도는 항상 비 const 버전을 선택합니다. const 버전을 얻으려면 캐스트를 수행해야합니다. 명확히하기 위해 게시물을 업데이트했습니다.
Nicol Bolas

15
나는 C ++ 11에 대해이 기능을 만든 후에 설명 할 수 있다고 생각했습니다.) Xeo는 유형이 변경되지 않는다고 주장하는 것이 옳 *this습니다. 그러나 혼란의 원인은 이해할 수 있습니다. 이는 ref 한정자가 오버로드 확인 및 함수 호출 중에 "this"(의도 한 목적으로 인용)! 개체가 바인딩되는 암시 적 (또는 "숨겨진") 함수 매개 변수 유형을 변경하기 때문입니다. 따라서 *thisXeo가 설명하는 것처럼 수정되었으므로 변경되지 않았습니다 . 대신 const함수 한정자가 만드는 것처럼 "hiddden"매개 변수를 변경하여 lvalue- 또는 rvalue-reference로 만듭니다 const.
bronekk
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.