Auto && bize ne anlatıyor?


169

Eğer kodu okursanız

auto&& var = foo();

burada fooherhangi bir işlev tür değerine göre döner T. Sonra vartür değeri referansı bir lvalue olduğunu T. Peki bu ne anlama geliyor var? Bu, kaynaklarını çalmamıza izin verildiği anlamına mı geliyor var? auto&&Kodunuzu okuyucunuza, unique_ptr<>özel sahipliğiniz olduğunu söylemek için geri döndüğünüzde yaptığınız gibi bir şey söylemek için kullanmanız gereken makul durumlar var mı? Peki ya sınıf tipi T&&ne zaman T?

Sadece auto&&şablon programlamasından başka kullanım durumları varsa anlamak istiyorum ; bu makaledeki örneklerde tartışılanlar gibi Scott Meyers tarafından yapılan Evrensel Referanslar .


1
Aynı şeyi merak ediyordum. Tür indiriminin nasıl çalıştığını anlıyorum, ancak kullandığımda kodum ne diyorauto&& ? Ben döngü için bir aralık tabanlı neden auto&&örnek olarak kullanmak için genişledi , ama bunun etrafında elde değil bakmayı düşünüyorum . Belki kim cevaplar açıklayabilir.
Joseph Mansfield

1
Bu yasal mı? Yani T uyuşmazlığı foogeri döndükten hemen sonra imha edilir, UB'den kulağa benzeyen bir rvalue ref saklar.

1
@aleguna Bu tamamen yasal. Bir başvuru veya işaretçi yerel bir değişkene, ancak bir değer döndürmek istemiyorum. Fonksiyonu foogibi örnek görünüm için kudreti: int foo(){return 1;}.
MWid

9
@aleguna, geçici arayüzlere yapılan başvurular, tıpkı C ++ 98'de olduğu gibi, ömür boyu uzantı gerçekleştirir.
ecatmur

5
@aleguna ömür boyu uzatma, yalnızca yerel geçici programlarla çalışır, başvuru döndüren işlevlerle çalışmaz. Bkz. Stackoverflow.com/a/2784304/567292
ecatmur

Yanıtlar:


233

Şunu kullanarak auto&& var = <initializer>şunları söylüyorsunuz: Bir lvalue veya rvalue ifadesi olup olmadığına bakılmaksızın herhangi bir başlatıcıyı kabul edeceğim ve sabitliğini koruyacağım . Bu genellikle yönlendirme için kullanılır (genellikle ile T&&). Bunun çalışmasının nedeni, "evrensel referans" auto&&veya herhangi bir şeyeT&& bağlanmasıdır .

Neden sadece bir kullanmak iyi diyebilirsiniz const auto&o çünkü aynı zamanda hiçbir şeye bağlamak? Bir constreferans kullanmayla ilgili problem, referans olması const! Daha sonra const olmayan referanslara bağlayamaz veya işaretlenmemiş üye işlevlerini çağıramazsınız const.

Örnek olarak, a std::vectoralmak, ilk öğesine bir yineleyici almak ve bu yineleyicinin işaret ettiği değeri bir şekilde değiştirmek istediğinizi düşünün :

auto&& vec = some_expression_that_may_be_rvalue_or_lvalue;
auto i = std::begin(vec);
(*i)++;

Bu kod, başlatıcı ifadesinden bağımsız olarak iyi bir şekilde derlenecektir. auto&&Aşağıdaki şekillerde başarısız olmanın alternatifleri :

auto         => will copy the vector, but we wanted a reference
auto&        => will only bind to modifiable lvalues
const auto&  => will bind to anything but make it const, giving us const_iterator
const auto&& => will bind only to rvalues

Bunun için auto&&mükemmel çalışıyor! Bunun auto&&gibi bir örnek, aralık tabanlı bir fordöngüdedir. Daha fazla ayrıntı için diğer soruma bakın.

Daha sonra referansta bunu bir lvalue veya bir rvalue olduğu gerçeğini korumak için kullanırsanız std::forward, auto&&kodunuz diyor: Şimdi nesnenizi bir lvalue veya rvalue ifadesinden aldığım için, orijinal olarak hangi değeri koruduğumu korumak istiyorum en verimli şekilde kullanabilirim - bu geçersiz kılabilir. De olduğu gibi:

auto&& var = some_expression_that_may_be_rvalue_or_lvalue;
// var was initialized with either an lvalue or rvalue, but var itself
// is an lvalue because named rvalues are lvalues
use_it_elsewhere(std::forward<decltype(var)>(var));

Bu use_it_elsewhere, orijinal başlatıcı değiştirilebilen bir değerken performans uğruna (kopyalardan kaçınarak) bağırsaklarını yırtmaya izin verir .

Bu, kaynakları çalabileceğimiz veya ne zaman çalabileceğimiz konusunda ne anlama geliyor var? Eh bu yana auto&&hiçbir şeye bağlanacak, muhtemelen söküp deneyemiyoruz varbağırsaklar kendimizi s - çok iyi, hatta const bir lvalue olabilir ya edilebilir. Bununla birlikte std::forward, içerilerini tamamen tahrip edebilecek diğer işlevlere de götürebiliriz . Bunu yapar yapmaz, vargeçersiz bir durumda olmayı düşünmeliyiz .

Şimdi bunu auto&& var = foo();, sorunuzda verildiği gibi, foo'nun bir Tby değeri döndürdüğü duruma uygulayalım . Bu durumda türünün türüne göre varkesin olarak kesinleştirileceğini biliyoruz T&&. Bunun bir değer olduğunu kesin olarak bildiğimiz için std::forward, kaynaklarını çalmak için izne ihtiyacımız yok . Bu özel durumda, değere göre foogeri döndüğünü bilerek , okuyucu bunu şu şekilde okumalıdır: Geri gönderilen geçici için bir rvalue referansı alıyorum foo, böylece mutlu bir şekilde hareket edebiliyorum.


Bir zeyilname olarak, some_expression_that_may_be_rvalue_or_lvalue"iyi bir kod değişebilir" durumu dışında, böyle bir ifade ne zaman ortaya çıkabilir söz etmeye değer olduğunu düşünüyorum . İşte burada tartışmalı bir örnek:

std::vector<int> global_vec{1, 2, 3, 4};

template <typename T>
T get_vector()
{
  return global_vec;
}

template <typename T>
void foo()
{
  auto&& vec = get_vector<T>();
  auto i = std::begin(vec);
  (*i)++;
  std::cout << vec[0] << std::endl;
}

Burada, get_vector<T>()genel türe bağlı olarak bir değer veya değer olabilen bu güzel ifade T. Biz esas olarak dönüş türünü get_vectorşablon parametresi aracılığıyla değiştiriyoruz foo.

Dediğimiz zaman foo<std::vector<int>>, get_vectordönecektir global_vecbir rvalue ifade verir değeriyle. Alternatif olarak, çağırdığımızda foo<std::vector<int>&>, referansla get_vectorgeri dönerek global_veclvalue ifadesine yol açar.

Eğer yaparsak:

foo<std::vector<int>>();
std::cout << global_vec[0] << std::endl;
foo<std::vector<int>&>();
std::cout << global_vec[0] << std::endl;

Beklendiği gibi aşağıdaki çıktıyı alıyoruz:

2
1
2
2

Değiştirmek olsaydı auto&&herhangi birine kodunda auto, auto&, const auto&veya const auto&&o zaman istediğimiz sonucu elde olmayacaktır.


auto&&Referansınızın bir değer veya rvalue ifadesi ile başlatılıp başlatılmadığına bağlı olarak program mantığını değiştirmenin alternatif bir yolu, tür özelliklerini kullanmaktır:

if (std::is_lvalue_reference<decltype(var)>::value) {
  // var was initialised with an lvalue expression
} else if (std::is_rvalue_reference<decltype(var)>::value) {
  // var was initialised with an rvalue expression
}

2
T vec = get_vector<T>();Foo fonksiyonunun içinde söyleyemeyiz mi? Yoksa saçma bir seviyeye sadeleştiriyorum :)
Asterisk

@Asterisk Hayır bcoz T vec yalnızca std :: vector <int &> durumunda lvalue'ye atanabilir ve T std :: vector <int> ise, verimsiz değere göre çağrı kullanacağız
Kapil

1
auto & bana aynı sonucu verir. MSVC 2015 kullanıyorum. GCC hata veriyor.
Sergey Podobry

Burada MSVC 2015 kullanıyorum, auto & auto && ile aynı sonuçları veriyor.
Kehe CAI

İnt neden; otomatik && j = i; izin verildi ancak int i; int && j = i; değil ?
SeventhSon84

14

İlk olarak, bu cevabımı evrensel referanslar için şablon argüman çıkarımının nasıl çalıştığına dair adım adım bir açıklama için yan okuma olarak okumanızı tavsiye ederim .

Bu, kaynaklarını çalmamıza izin verildiği anlamına mı geliyor var?

Şart değil. Ya foo()bir bütün ani bir başvuru döndü ya aramayı değiştirdi ama kullanımını güncellemek için unuttum var? Veya genel koddaysanız ve dönüş türü foo()parametrelerinize bağlı olarak değişebilir mi?

Düşünün auto&&tam olarak aynı olmasını T&&in template<class T> void f(T&& v);, çünkü en (yaklaşık ) tam olarak bu. Bunları iletmeniz veya herhangi bir şekilde kullanmanız gerektiğinde, işlevlerdeki evrensel referanslarla ne yaparsınız? std::forward<T>(v)Orijinal değer kategorisini geri almak için kullanılır . Eğer fonksiyonunuza geçmeden önce bir değerse, geçtikten sonra bir değer kalır std::forward. Eğer bir değerse, yine bir değer haline gelecektir (unutmayın, adlandırılmış bir değer referansı bir değerdir).

Peki, varjenerik bir tarzda nasıl doğru kullanıyorsunuz ? Kullanın std::forward<decltype(var)>(var). Bu, std::forward<T>(v)yukarıdaki işlev şablonundaki ile aynı şekilde çalışacaktır . A ise var, bir T&&geri değer alırsınız ve eğer T&bir geri değer alırsınız.

Peki, konuya dönelim: Kod tabanında auto&& v = f();ve std::forward<decltype(v)>(v)kodlar bize ne anlatıyor? Bize bunun ven verimli şekilde kazanılacağını ve aktarılacağını söylüyorlar . Bununla birlikte, böyle bir değişkeni ilettikten sonra taşındığının mümkün olduğunu unutmayın, bu yüzden sıfırlamadan daha fazla kullanmak yanlış olur.

Şahsen ben kullanmak auto&&ben gerektiğinde genel kodunda modifyable değişkeni. Bir rvalue mükemmel şekilde iletiliyor, çünkü hareket operasyonu potansiyel olarak bağırsaklarını çalıyor. Sadece tembel olmak istiyorsam (yani, biliyorum bile olsa tip adını hecelemiyorum) ve değiştirmeye gerek yoksa (örneğin, bir aralığın elemanlarını yazdırırken), yapışacağım auto const&.


autoo ana kadar farklı olduğunu auto v = {1,2,3};yapacak vbir std::initializer_listiken, f({1,2,3})bir kesinti hatası olacaktır.


Cevabınızın ilk bölümünde: Demek istediğim, foo()bir değer türü döndürürse T, var(bu ifade) bir değer ve onun türü (bu ifadenin) T(yani T&&) için bir değer referansı olacaktır .
MWid

@MWid: Mantıklı, ilk kısmı kaldırıldı.
Xeo

3

TBir hareket yapıcısı olan bir tür düşünün ve

T t( foo() );

o hareket yapıcısını kullanır.

Şimdi, dönüşü yakalamak için bir ara referans kullanalım foo:

auto const &ref = foo();

bu, move yapıcısının kullanılmasını engeller, bu nedenle döndürülen değerin taşınmak yerine kopyalanması gerekir ( std::moveburada kullansak bile , aslında bir const ref ile ilerleyemeyiz)

T t(std::move(ref));   // invokes T::T(T const&)

Ancak,

auto &&rvref = foo();
// ...
T t(std::move(rvref)); // invokes T::T(T &&)

hareket oluşturucu hala kullanılabilir.


Ve diğer sorularınızı ele almak için:

... auto && kodunu okuyucunuza kodunuza bir şey söylemek için kullanmanız gereken makul durumlar var mı?

Xeo'nun dediği gibi ilk şey, X'i X türü ne olursa olsun, X'i olabildiğince verimli bir şekilde geçiriyorum . Bu nedenle, auto&&dahili olarak kullanılan kodu görmek, uygun olduğu durumlarda dahili olarak hareket semantiği kullanacağını bildirmelidir.

... özel sahipliğiniz olduğunu söylemek için unique_ptr <> döndürdüğünüz gibi ...

Bir işlev şablonu bir tür bağımsız değişkeni aldığında, ilettiğiniz T&&nesneyi taşıyabileceğini söylüyor. unique_ptrAçıkça geri dönmek arayan kişiye sahiplik verir; kabul T&&edilebilir kaldırmak (bir hareket ctor varsa, vs.), arayıcıdan sahibi.


2
İkinci örneğinizin geçerli olduğundan emin değilim. Move yapıcısını çağırmak için mükemmel yönlendirmeye ihtiyacınız yok mu?

3
Bu yanlış. Her iki durumda da, kopya yapıcısı çağrılır, çünkü refve rvrefher ikisi de değerlerdir. Move yapıcısını istiyorsanız, o zaman yazmak zorundasınız T t(std::move(rvref)).
MWid

İlk örneğinizde const ref mi demek istediniz auto const &?
PiotrNycz

@aleguna - Siz ve MWid haklısınız, teşekkürler. Cevabımı düzelttim.
Yararsız

1
@Useless Haklısın. Ama bu sorularıma cevap vermiyor. Ne zaman kullanıyorsunuz auto&&ve kodunuzu okuyucuya ne kullanarak söylüyorsunuz auto&&?
MWid

-3

auto &&Sözdizimi C ++ 11 iki yeni özellik kullanır:

  1. autoBölüm bağlamında (bu durumda dönüş değeri) dayalı tip anlamak derleyici sağlar. Bu (istediğiniz belirtmek için izin herhangi bir referans nitelikleri olmadan T, T &ya T &&bir çıkarılmış türü için T).

  2. Bu &&yeni hareket semantiği. Taşıma semantiğini destekleyen bir tür T(T && other), içeriği yeni türde en iyi şekilde hareket ettiren bir kurucu uygular . Bu, bir nesnenin derin bir kopya yapmak yerine dahili gösterimi değiştirmesine izin verir.

Bu, aşağıdaki gibi bir şeye sahip olmanızı sağlar:

std::vector<std::string> foo();

Yani:

auto var = foo();

döndürülen vektörün bir kopyasını (pahalı) gerçekleştirir, ancak:

auto &&var = foo();

vektörün dahili gösterimini (vektör foove boş vektör var) değiştirir, böylece daha hızlı olur.

Bu, yeni for-loop sözdiziminde kullanılır:

for (auto &item : foo())
    std::cout << item << std::endl;

For-loop'un, auto &&döndürdüğü değere bir foove değeri itemiçerdiği her bir değere bir referans olduğu durumlarda foo.


Bu yanlış. auto&&hiçbir şey hareket etmeyecek, sadece referans yapacak. Bir lvalue veya rvalue referansı olup olmadığı, onu başlatmak için kullanılan ifadeye bağlıdır.
Joseph Mansfield

Çünkü her iki durumda da hareket yapıcısı, adı verilecek std::vectorve std::stringmoveconstructible bulunmaktadır. Bunun türüyle ilgisi yoktur var.
MWid

1
@MWid: Aslında, kopyalama / taşıma yapıcısına yapılan çağrı da RVO ile birlikte yapılabilir.
Matthieu M.

@MatthieuM. Haklısın. Ancak yukarıdaki örnekte, kopya oluşturucu asla çağrılmayacağını düşünüyorum, çünkü her şey hareket inşa edilebilir.
MWid

1
@MWid: Demek istediğim, hareket oluşturucunun bile seçilebiliyor olmasıydı. Elizyon hamle yapar (daha ucuzdur).
Matthieu M.Kasım
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.