Co to jest "rvalue reference for * this"?

W clang ' s C++11 status page pojawiła się propozycja o nazwie "rvalue reference for *this".

Przeczytałem sporo o referencjach rvalue i je zrozumiałem, ale chyba o tym Nie wiem. Nie mogłem również znaleźć zbyt wielu zasobów w Internecie za pomocą warunków.

Na stronie znajduje się link do artykułu z propozycją: N2439 (Rozszerzenie semantyki move NA *this), ale też nie dostaję stamtąd zbyt wielu przykładów.

Co to jest temat?

Author: Rapptz, 2011-12-22

3 answers

Po pierwsze, "kwalifikatory ref do * this" to tylko "oświadczenie marketingowe". Typ *this nigdy się nie zmienia, patrz na dole tego postu. O wiele łatwiej jest to zrozumieć za pomocą tego sformułowania.

Następnie następujący kod wybiera funkcję do wywołania na podstawie kwalifikatora ref "niejawnego parametru obiektu" funkcji:

// 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
}

Wyjście:

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

Całość jest zrobiona, aby umożliwić Ci skorzystanie z faktu, gdy obiekt, na którym jest wywoływana funkcja, jest wartością rvalue (na przykład bez nazwy tymczasową). Weźmy poniższy kod jako kolejny przykład:

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);
  }
};

To może być trochę wymyślone, ale powinieneś zrozumieć.

Zauważ, że możesz połączyć kwalifikatory cv (const and volatile) and ref-qualifiers (& i &&).


Uwaga: wiele standardowych cudzysłowów i Wyjaśnienie rozdzielczości przeciążenia poniżej!

† zrozumieć, jak to działa i dlaczego odpowiedź @ Nicol Bolas jest przynajmniej częściowo błędna, musimy trochę poszperać w standardzie C++ (część wyjaśniająca, dlaczego odpowiedź @ Nicol jest błędna, jest na dole, jeśli tylko cię to interesuje).

Która funkcja zostanie wywołana jest określona przez proces o nazwie rozdzielczość przeciążenia . Proces ten jest dość skomplikowany, więc dotkniemy tylko tego, co jest dla nas ważne.

Po pierwsze, ważne jest, aby zobaczyć, jak przeciążenie rozdzielczości dla funkcji Członkowskich działa:

§13.3.1 [over.match.funcs]

P2 zbiór funkcji kandydujących może zawierać zarówno funkcje Członkowskie, jak i nie-członkowskie, które mają być rozwiązane na podstawie tej samej listy argumentów. Tak więc, że listy argumentów i parametrów są porównywalne w obrębie tego heterogenicznego zbioru, uważa się, że funkcja member ma dodatkowy parametr, zwany implicit object parameter, który reprezentuje obiekt, dla którego funkcja member została wywołana. [...]

P3 context może tworzyć listę argumentów, która zawiera argument obiektu implikowanego , aby określić obiekt, na którym ma być operowany.

Dlaczego w ogóle musimy porównywać funkcje member i non-member? Operator przeciąża, dlatego. Rozważmy to:

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

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

Na pewno chciałbyś, aby poniższe wywołały funkcję free, prawda?

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

Dlatego funkcje member i non-member są zawarte w tzw. overload-set. Aby zmniejszyć rozdzielczość skomplikowana, pogrubiona część standardowego cytatu istnieje. Dodatkowo, jest to dla nas ważny bit (ta sama klauzula):

P4 dla niestatycznych funkcji prętowych, typ parametru obiektu implicit to

  • "lvalue reference to cv X" dla funkcji deklarowanych bez kwalifikatora ref lub z & ref-qualifier

  • " rvalue reference to cv X" dla funkcji deklarowanych z && ref-qualifier

Gdzie X jest klasą, której funkcja jest członkiem, a CV jest kwalifikacją cv w deklaracji funkcji członka. [...]

P5 podczas rozdzielczości przeciążenia [...] [t] he implicit object parameter [...] zachowuje swoją tożsamość, ponieważ konwersje na odpowiedni argument muszą być zgodne z tymi dodatkowymi regułami:

  • Żaden obiekt tymczasowy nie może być wprowadzony do przechowywania argumentu dla implicit parametr obiektu; oraz

  • Nie można zastosować konwersji zdefiniowanych przez użytkownika, aby uzyskać dopasowanie typu z nim

[...]

(ostatni bit oznacza po prostu, że nie można oszukać rozdzielczości przeciążenia na podstawie ukrytych konwersji obiektu, na którym jest wywoływana funkcja (lub operator).)

Weźmy pierwszy przykład na górze tego postu. Po wspomnianej transformacji zestaw przeciążeniowy wygląda jak to:

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

Następnie lista argumentów, zawierająca argument obiektu implikowanego , jest porównywana z listą parametrów każdej funkcji zawartej w zestawie przeciążeń. W naszym przypadku lista argumentów będzie zawierać tylko ten argument obiektu. Zobaczmy, jak to wygląda:

// 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

Jeśli po przetestowaniu wszystkich przeciążeń w zbiorze zostanie tylko jedno, to rozdzielczość przeciążenia się powiedzie i wywołana zostanie funkcja powiązana z tym przeciążeniem. To samo dotyczy drugiego wywołanie na "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

Zauważ jednak, że gdybyśmy nie podali ref-qualifier (i jako taki nie przeciążali funkcji), to f1 czy pasuje do wartości rvalue (nadal §13.3.1):

P5 [...] Dla niestatycznych funkcji Członkowskich zadeklarowanych bez kwalifikatora ref , stosuje się dodatkową regułę:

  • nawet jeśli parametr obiektu ukrytego nie jest const-kwalifikowany, wartość R może być powiązana z parametrem tak długo, jak pod wszystkimi innymi względami argument może być przekonwertowany na typ obiektu niejawnego parametru.
struct test{
  void f() { std::cout << "lvalue or rvalue object\n"; }
};

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

Teraz, dlaczego odpowiedź @ Nicol jest przynajmniej częściowo błędna. Mówi:

Należy zauważyć, że niniejsza deklaracja zmienia typ *this.

To jest złe, *this jest zawsze lvalue:

§5.3.1 [expr.unary.op] p1

Operator unary * wykonuje indirection: wyrażenie, do którego jest stosowany, jest wskaźnikiem do typu obiektu, lub wskaźnik do typu funkcji , a wynikiem jest lvalue odnoszący się do obiektu lub funkcji, na które wskazuje wyrażenie.

§9.3.2 [class.this] p1

W ciele niestatycznej (9.3) funkcji składowej, słowo kluczowe this jest wyrażeniem prvalue, którego wartością jest adres obiektu, dla którego funkcja jest wywoływana. Typ this w funkcji członka klasy X to X*. [...]

 295
Author: Xeo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-06-20 09:12:55

Istnieje dodatkowy przypadek użycia formularza kwalifikującego ref lvalue. C++98 posiada język, który pozwala na wywołanie funkcji nie - const dla instancji klasy, które są wartościami r. Prowadzi to do wszelkiego rodzaju dziwactwa, które jest sprzeczne z samą koncepcją rvalness i odbiega od tego, jak działają wbudowane typy: {]}

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 ref-qualifiers rozwiązują te problemy:

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

Teraz operatory działają jak te z wbudowanych typów, akceptując tylko lvalues.

 78
Author: JohannesD,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-12-23 09:14:10

Załóżmy, że masz dwie funkcje na klasie, obie o tej samej nazwie i podpisie. Ale jedno z nich jest zadeklarowane const:

void SomeFunc() const;
void SomeFunc();

Jeśli instancja klasy nie jest const, rozdzielczość przeciążenia wybierze preferencyjnie wersję non-const. Jeśli instancją jest const, użytkownik może wywołać tylko wersję const. A wskaźnik this jest wskaźnikiem const, więc instancji nie można zmienić.

Co "r-value reference for this" pozwala na dodanie kolejnego "alternatywa": {]}

void RValueFunc() &&;

To pozwala mieć funkcję, która może tylko być wywołana, jeśli użytkownik wywoła ją przez odpowiednią wartość r. Jeśli więc jest to w typie Object:

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

W ten sposób można dostosować zachowanie na podstawie tego, czy obiekt jest dostępny za pomocą wartości r, czy nie.

Zauważ, że nie można przeciążać między wersjami referencyjnymi wartości r I wersjami niereferencyjnymi. Oznacza to, że jeśli masz nazwę funkcji członka, wszystkie jej wersje albo używają kwalifikatorów wartości l/R na this, albo żadna z nich tego nie robi. Nie możesz tego zrobić:

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

Musisz to zrobić:

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

Należy zauważyć, że niniejsza deklaracja zmienia typ *this. Oznacza to, że && wersje wszystkich członków dostępu jako odniesienia do wartości r. Staje się więc możliwe łatwe przemieszczanie się z wnętrza obiektu. Przykład podany w pierwszej wersji wniosku jest (uwaga :poniższe mogą nie być poprawne z finalną wersją C++11; jest prosto z początkowa "wartość r z tej propozycji"):

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
 28
Author: Nicol Bolas,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-12-23 18:03:27