“Bu * için rvalue referansı” nedir?


238

Clang'ın C ++ 11 durum sayfasında "this * için rvalue referansı" adlı bir teklifle karşılaştım .

Rvalue referanslarını biraz okudum ve anladım, ama bunu bildiğimi sanmıyorum. Ayrıca terimleri kullanarak web üzerinde çok fazla kaynak bulamadım.

Sayfadaki teklif belgesine bir bağlantı var: N2439 (Hareket semantiğini * buna genişletme), ancak oradan da fazla örnek almıyorum.

Bu özellik ne hakkında?

Yanıtlar:


293

Birincisi, "bu * için ref-elemeleri" sadece bir "pazarlama açıklamasıdır". *thisAsla değişmeyen tür, bu yazının altına bakın. Yine de bu ifadelerle anlaşılması çok daha kolay.

Ardından, aşağıdaki kod, işlevinin "örtük nesne parametresi" nin ref niteleyicisine dayalı olarak çağrılacak işlevi seçer :

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

Çıktı:

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

Her şey, işlevin çağrıldığı nesnenin bir değer olduğu (örneğin adsız geçici) olduğu gerçeğinden yararlanmanıza izin vermek için yapılır. Başka bir örnek olarak aşağıdaki kodu ele alalım:

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

Bu biraz çelişkili olabilir, ama fikri almalısınız.

Eğer birleştirebilirsiniz o Not cv-eleme ( constve volatile) ve ref-eleme ( &ve &&).


Not: Buradan sonra birçok standart tırnak ve aşırı yük çözünürlüğü açıklaması!

† Bunun nasıl çalıştığını ve @Nicol Bolas'ın cevabının en azından kısmen yanlış olduğunu anlamak için C ++ standardını biraz kazmamız gerekiyor (eğer @ Nicol'in cevabının neden yanlış olduğunu açıklayan kısım en altta, eğer sadece bununla ilgilenir).

Hangi fonksiyonun çağırılacağı aşırı yük çözünürlüğü adı verilen bir süreç tarafından belirlenir . Bu süreç oldukça karmaşıktır, bu yüzden sadece bizim için önemli olan noktaya dokunacağız.

İlk olarak, üye işlevleri için aşırı yük çözünürlüğünün nasıl çalıştığını görmek önemlidir:

§13.3.1 [over.match.funcs]

p2 Aday işlevleri kümesi, aynı bağımsız değişken listesine göre çözümlenecek hem üye hem de üye olmayan işlevleri içerebilir. Bu argüman ve parametre listelerinin bu heterojen kümede karşılaştırılabilmesi için, bir üye işlevinin, üye işlevinin çağrıldığı nesneyi temsil eden, örtük nesne parametresi olarak adlandırılan ekstra bir parametreye sahip olduğu kabul edilir . [...]

p3 Benzer şekilde, uygun olduğunda, bağlam, üzerinde çalışılacak nesneyi göstermek için zımni bir nesne bağımsız değişkeni içeren bir bağımsız değişken listesi oluşturabilir .

Neden üye ve üye olmayan işlevleri karşılaştırmamız gerekiyor? Operatör aşırı yükleme, bu yüzden. Bunu düşün:

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

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

Aşağıdakilerin kesinlikle ücretsiz işlevi çağırmasını istersiniz, değil mi?

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

Bu nedenle üye ve üye olmayan işlevler aşırı yük setine dahil edilir. Çözünürlüğü daha az karmaşık hale getirmek için standart teklifin kalın kısmı vardır. Ayrıca, bu bizim için önemli olan nokta (aynı fıkra):

p4 Statik olmayan üye işlevleri için örtük nesne parametresinin türü

  • “İçin lvalue referans cv X işlevleri için” bir olmadan ilan ref-eleme veya & ref-eleme

  • “İçin rvalue referans cv X işlevleri için” ile beyan && ref-eleme

burada Xişlev üyesi olan ve sınıfıdır ev elemanı işlev bildiriminde cv-yeterlilik. [...]

p5 Aşırı yük çözünürlüğü sırasında [...] [t] nesne parametresini [...] örtük kılar, çünkü ilgili argümandaki dönüşümler şu ek kurallara uyacaktır:

  • örtük nesne parametresi için bağımsız değişkeni tutacak hiçbir geçici nesne sokulamaz; ve

  • onunla bir tür eşleşmesi elde etmek için kullanıcı tanımlı hiçbir dönüşüm uygulanamaz

[...]

(Son bit, bir üye işlevinin (veya operatörün) çağrıldığı nesnenin örtülü dönüşümlerine dayanarak aşırı yük çözünürlüğünü aldatamayacağınız anlamına gelir.)

Bu yazının üst kısmındaki ilk örneği ele alalım. Yukarıda bahsedilen dönüşümden sonra, aşırı yük seti şöyle görünür:

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

Daha sonra, örtük bir nesne argümanı içeren argüman listesi, aşırı yük kümesinde bulunan her fonksiyonun parametre listesiyle karşılaştırılır. Bizim durumumuzda, bağımsız değişken listesi yalnızca bu nesne bağımsız değişkenini içerecektir. Bunun nasıl göründüğüne bakalım:

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

Setteki tüm aşırı yükler test edildikten sonra, yalnızca bir tanesi kalırsa, aşırı yük çözünürlüğü başarılı olur ve dönüştürülen aşırı yüke bağlı işlev çağrılır. Aynı şey ikinci 'f' çağrısı için de geçerlidir:

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

Not Ancak, herhangi sağladığının ref-eleme yani (ve haliyle işlevi aşırı değil) f1 olur bir rvalue (hala maç §13.3.1):

p5 [...] Ref niteleyicisi olmadan bildirilen statik olmayan üye işlevleri için ek bir kural geçerlidir:

  • örtük nesne parametresi const-kalifiye edilmemiş olsa bile, diğer tüm açılardan argüman örtük nesne parametresinin türüne dönüştürülebildiği sürece parametreye bir rvalue bağlanabilir.
struct test{
  void f() { std::cout << "lvalue or rvalue object\n"; }
};

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

Şimdi, @ Nicol'in cevabının neden en azından kısmen yanlış olduğuna. Diyor:

Bu bildirimin türünü değiştirdiğini unutmayın *this.

Bu yanlış, *thisher zaman bir değerdir:

§5.3.1 [expr.unary.op] p1

Tekli *operatör dolaylı işlem gerçekleştirir : uygulandığı ifade, bir nesne tipine bir işaretçi veya bir işlev tipine bir işaretçi olacaktır ve sonuç, ifadenin işaret ettiği nesneye veya işleve atıfta bulunan bir değerdir .

§9.3.2 [class.this] p1

Statik olmayan (9.3) üye işlevinin gövdesinde, anahtar sözcük thisdeğeri işlevin çağrıldığı nesnenin adresi olan bir ön değer ifadesidir. Tipi thisbir sınıfın bir üyesi işlevinde Xolduğu X*. [...]


"Dönüşümden sonra" bölümünün hemen ardından paraneter türlerinin "test" yerine "foo" olması gerektiğine inanıyorum.
ryaner

@ryaner: İyi bul, teşekkürler. Gerçi parametre değil, fonksiyonların sınıf tanımlayıcısı yanlıştır. :)
Xeo

Üzgünüz, bu bölümü okuduğumda test adlı oyuncak sınıfını unuttum ve f'nin foo içinde bulunduğunu düşündüm, bu yüzden benim yorumum ..
ryaner

Bu yapıcılar ile yapılabilir MyType(int a, double b) &&mi?
Germán Diago

2
"Bu asla değişmeyen * türü" r / l değeri yeterliliğine bağlı olarak değişmeyeceğinden biraz daha açık olmalısınız. ancak const / const olmayanlar arasında değişebilir.
xaxxon

78

Lvalue ref-niteleyici formu için ek bir kullanım durumu vardır. C ++ 98, değer olmayan constsınıf örnekleri için üye olmayan işlevlerin çağrılmasına izin veren bir dile sahiptir . Bu, değerlilik kavramına aykırı olan ve yerleşik türlerin nasıl çalıştığından sapan her türlü garipliğe yol açar:

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 niteleyicileri şu sorunları çözer:

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

Şimdi operatörler sadece lvaleleri kabul ederek yerleşik tipler gibi çalışırlar.


28

Diyelim ki bir sınıfta hem aynı ada hem de imzalı iki fonksiyonunuz var. Ancak bunlardan biri ilan edildi const:

void SomeFunc() const;
void SomeFunc();

Bir sınıf örneği değilse const, aşırı yük çözünürlüğü tercihen sabit olmayan sürümü seçer. Örnek buysa, constkullanıcı yalnızca constsürümü çağırabilir . Ve thisişaretçisi olan constörnek değiştirilemez böylece işaretçi.

Bunun için "r-değeri referansı" ne yaparsa başka bir alternatif eklemenize izin verir:

void RValueFunc() &&;

Bu, yalnızca kullanıcı uygun bir r değeri üzerinden çağırdığında çağrılabilecek bir işleve sahip olmanızı sağlar . Yani bu türdeyse Object:

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

Bu şekilde, nesneye bir r değeri üzerinden erişilip erişilmediğine bağlı olarak davranışı özelleştirebilirsiniz.

R değeri referans sürümleri ile referans olmayan sürümler arasında aşırı yüklenmeye izin verilmediğini unutmayın. Yani, üye işlev adınız varsa, tüm sürümlerinde l / r değeri niteleyicileri kullanılır thisveya hiçbiri kullanılmaz. Bunu yapamazsın:

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

Bunu yapmalısın:

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

Bu bildirimin türünü değiştirdiğini unutmayın *this. Bu, &&sürümlerin tüm üyelere r-değeri referansları olarak eriştiği anlamına gelir . Böylece nesnenin içinden kolayca hareket etmek mümkün olur. Teklifin ilk sürümünde verilen örnek şöyledir (not: aşağıdakiler C ++ 11'in son sürümüyle doğru olmayabilir; doğrudan bu "teklifin ilk" r-değerinden):

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
Sanırım std::moveikinci versiyona ihtiyacınız var , değil mi? Ayrıca, neden değer referansı geri dönüyor?
Xeo

1
@Xeo: Çünkü teklifte örnek buydu; Halen mevcut sürümle çalışıp çalışmadığı hakkında hiçbir fikrim yok. Ve r-değeri referans geri dönüşünün nedeni, hareketin onu yakalayan kişiye kadar olması gerektiğidir. Henüz gerçekleşmemelidir, sadece onu bir değer yerine bir &&'de saklamak isterse.
Nicol Bolas

3
Evet, ikinci sorumun nedenini düşündüm. Acaba, geçici bir üyeye yapılan bir rvalue referansı, bu geçici sürenin ömrünü uzatıyor mu, yoksa bir üyesine mi? Yemin edebilirim ki SO hakkında bir süre önce bir soru gördüm ...
Xeo

1
@Xeo: Bu tamamen doğru değil. Aşırı yük çözünürlüğü, varsa sabit olmayan sürümü her zaman seçer. Const sürümünü almak için bir oyuncu seçimi yapmanız gerekir. Açıklığa kavuşturmak için yayını güncelledim.
Nicol Bolas

15
Sonuçta C ++ 11 için bu özelliği yarattığımı açıklayabileceğimi düşündüm;) Xeo, türünü değiştirmediğinde ısrar ediyor *this, ancak karışıklığın nereden geldiğini anlayabiliyorum. Bunun nedeni, ref-niteleyicinin aşırı yük çözünürlüğü ve işlev çağrısı sırasında "this" (burada amaçlanan tırnak işaretleri!) Nesnesinin bağlı olduğu örtük (veya "gizli") işlev parametresinin türünü değiştirmesidir. Yani, *thisXeo'nun açıkladığı gibi, bu sabit olduğu için hiçbir değişiklik yok . Bunun yerine "hiddden" parametresinin değiştirilmesi gibi, değer nitelendirme veya rvalue referansı yapmak için, constişlev niteleyicisinin constvb.
Yaptığı
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.