簡體   English   中英

auto &&告訴我們什么?

[英]What does auto&& tell us?

如果您閱讀類似的代碼

auto&& var = foo();

其中foo是按T類型的值返回的任何函數。 var是對T右值引用的左值。 但這對var意味着什么? 這是否意味着我們被允許竊取var的資源? 是否有任何合理的情況,當您使用auto&&告訴代碼的讀者時,就像您返回unique_ptr<>來告訴您具有獨占所有權一樣? 而當T是類類型時,例如T&&呢?

我只是想了解一下,除了模板編程中的auto&&以外,還有其他用例auto&& 就像Scott Meyers在本文“ 通用參考”中的示例中所討論的一樣。

通過使用auto&& var = <initializer>您的意思是: 我將接受任何初始化程序,無論它是左值表達式還是右值表達式,並且我將保留其constness 通常用於轉發 (通常與T&& )。 之所以起作用,是因為“通用引用”( auto&&T&& )將綁定到任何東西

你可能會說,好吧,為什么不直接使用const auto& ,因為這將綁定到什么? 使用const引用的問題在於它是const 您以后將無法將其綁定到任何非const引用或調用未標記為const任何成員函數。

例如,假設您要獲取std::vector ,將迭代器帶到其第一個元素,然后以某種方式修改該迭代器指向的值:

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

無論初始化表達式如何,此代碼都可以正常編譯。 auto&&的替代方法以下列方式失敗:

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

因此, auto&&可以完美運行! 像這樣使用auto&&的示例是在基於范圍的for循環中。 有關更多詳細信息,請參見我的其他問題

如果然后在auto&&引用上使用std::forward保留原來是左值或右值的事實,則代碼會說: 現在,我已經從左值或右值表達式中獲取了對象,我想保留其最初具有的任何價值,以便我可以最有效地使用它-這可能會使它失效。 如:

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

當原始的初始值設定項是可修改的右值時,這可以使use_it_elsewhere其膽量以提高性能(避免復制)。

對於我們是否可以或何時可以從var竊取資源,這意味着什么? 好吧,既然auto&&將綁定到任何東西,我們就不可能嘗試自己擺脫var guts-它很可能是左值甚至是const。 但是,我們可以將std::forward傳遞給可能完全破壞其內部的其他函數。 一旦這樣做,我們應該考慮var處於無效狀態。

現在,將其應用於auto&& var = foo(); ,如您的問題中給出的那樣,其中foo通過值返回T 在這種情況下,我們肯定知道var的類型將推導為T&& 由於我們可以肯定地知道它是一個右值,因此我們不需要std::forward的許可來竊取其資源。 在這種特定情況下, 知道foo按值返回 ,讀者應該將其讀取為: 我正在對foo返回的臨時值進行右值引用,因此我可以很高興地從中移出。


作為附錄,我認為值得一提的是諸如some_expression_that_may_be_rvalue_or_lvalue類的表達式可能出現,而不是出現“代碼可能會更改”的情況。 所以這是一個人為的例子:

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

在這里, get_vector<T>()是一個可愛的表達式,根據泛型T ,它可以是左值或右值。 我們實質上是通過foo的template參數get_vector更改get_vector的返回類型。

當我們調用foo<std::vector<int>>get_vector將按值返回global_vec ,從而給出一個右值表達式。 另外,當我們調用foo<std::vector<int>&>get_vector將通過引用返回global_vec ,從而產生一個左值表達式。

如果這樣做:

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

正如預期的那樣,我們得到以下輸出:

2
1
2
2

如果將代碼中的auto&&更改為autoauto&const auto&const auto&&則我們將無法獲得所需的結果。


根據是使用左值表達式還是右值表達式初始化auto&&引用來更改程序邏輯的另一種方法是使用類型特征:

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
}

首先,我建議將我的答案作為側面閱讀,以逐步解釋通用引用的模板參數推導如何工作。

這是否意味着我們被允許竊取var的資源?

不必要。 如果foo()突然返回了引用,或者您更改了調用但忘了更新var的使用怎么辦? 或者,如果您使用的是通用代碼,並且foo()的返回類型可能會根據您的參數而變化?

認為auto&&template<class T> void f(T&& v);T&&完全相同template<class T> void f(T&& v); ,因為(幾乎是 )。 當您需要傳遞或以任何方式使用它們時,如何使用函數中的通用引用? 您使用std::forward<T>(v)返回原始值類別。 如果在傳遞給函數之前是左值,則在通過std::forward傳遞后將保持左值。 如果是右值,它將再次變為右值(請記住,命名的右值引用是左值)。

那么,如何以通用方式正確使用var 使用std::forward<decltype(var)>(var) 這將與上面的函數模板中的std::forward<T>(v)完全相同。 如果varT&& ,則將返回右值;如果是T& ,則將返回左值。

因此,回到主題: auto&& v = f();是什么? 代碼庫中的std::forward<decltype(v)>(v)告訴我們嗎? 他們告訴我們,將以最有效的方式獲取和傳遞v 但是請記住,在轉發了這樣的變量之后,可能會將其移出,因此在不重新設置它的情況下進一步使用它是不正確的。

就個人而言,我用auto&&在通用的代碼時,我需要一個modifyable變量。 完美轉發右值正在修改,因為移動操作可能會竊取其膽量。 如果我只是想變得懶惰(即即使我知道它也不要拼寫類型名稱)並且不需要修改(例如,僅打印范圍內的元素時),我會堅持使用auto const&


auto相差甚遠,因此auto v = {1,2,3}; 將使v成為std::initializer_list ,而f({1,2,3})將是推論失敗。

考慮一些具有移動構造函數的類型T ,並假設

T t( foo() );

使用該move構造函數。

現在,讓我們使用一個中間引用來捕獲foo的返回值:

auto const &ref = foo();

這會排除使用move構造函數,因此必須復制返回值,而不是移動它(即使我們在此處使用std::move ,我們也無法實際通過const ref進行移動)

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

但是,如果我們使用

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

move構造函數仍然可用。


並解決您的其他問題:

...在任何合理的情況下,您應該使用auto &&&告訴代碼的讀者...

正如Xeo所說,第一件事本質上無論X是什么類型, 我都盡可能高效地傳遞X。 因此,看到內部使用auto&&代碼應該傳達出在適當的地方內部將使用move語義的信息。

...就像您返回unique_ptr <>告訴您擁有專有所有權時一樣...

當函數模板接受類型為T&&的參數時,就是說它可能會移動您傳入的對象。返回unique_ptr顯式地賦予調用方所有權; 接受T&&可能會刪除呼叫者的所有權(如果存在搬家公司等)。

auto &&語法使用C ++ 11的兩個新功能:

  1. auto部分允許編譯器根據上下文(在這種情況下為返回值)推斷類型。 這沒有任何參考條件(允許您指定是否要對推導類型T TT &T && )。

  2. &&是新的動作語義。 支持移動語義的類型實現了構造函數T(T && other) ,該構造函數可以最佳地移動新類型中的內容。 這允許對象交換內部表示,而不是執行深層復制。

這使您可以像:

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

所以:

auto var = foo();

將執行返回的向量的副本(昂貴),但是:

auto &&var = foo();

將交換向量的內部表示形式(來自foo的向量和來自var的空向量),因此會更快。

這在新的for循環語法中使用:

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

其中,for循環將fooauto &&保留為foo的返回值,而item是對foo每個值的引用。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM