Auto && हमें क्या बताता है?


168

अगर आप जैसा कोड पढ़ते हैं

auto&& var = foo();

fooप्रकार के मान से लौटने वाला कोई भी कार्य कहां है T। फिर varटाइप टू रेवल्यू रेफरेंस का एक लैवल्यू है T। लेकिन इसके लिए क्या मतलब है var? क्या इसका मतलब है, हमें संसाधनों की चोरी करने की अनुमति है var? क्या कोई उचित परिस्थितियां हैं जब आपको auto&&अपने कोड के पाठक को कुछ बताने के लिए उपयोग करना चाहिए जैसे कि आप करते हैं जब आप unique_ptr<>यह बताते हैं कि आपके पास विशेष स्वामित्व है? और उदाहरण के लिए क्या है T&&जब Tवर्ग प्रकार का है?

मैं सिर्फ समझना चाहता हूं, अगर auto&&टेम्पलेट प्रोग्रामिंग में उन की तुलना में कोई अन्य उपयोग के मामले हैं ; स्कॉट मेयर्स द्वारा इस लेख यूनिवर्सल रेफरेंस में उदाहरणों पर चर्चा की गई।


1
मैं एक ही बात सोच रहा हूँ। मैं समझता हूं कि प्रकार कटौती कैसे काम करती है, लेकिन जब मैं उपयोग करता हूं तो मेरा कोड क्या कह रहा है auto&&? मैं यह देखने के बारे में सोच रहा हूं कि लूप के लिए एक श्रेणी-आधारित auto&&एक उदाहरण के रूप में उपयोग करने के लिए क्यों फैलता है , लेकिन इसके लिए गोल नहीं मिला है। शायद जो कोई भी इसका जवाब दे सकता है।
जोसेफ मैंसफील्ड

1
क्या यह भी कानूनी है? मेरा मतलब है कि टी के इंस्टेंट को fooरिटर्न के बाद तुरंत नष्ट कर दिया जाता है, यह एक आरवाई रेफरी को संग्रहीत करता है जो यूबी से लेकर नी तक लगता है।

1
@aleguna यह पूरी तरह से कानूनी है। मैं एक स्थानीय चर के लिए एक संदर्भ या सूचक वापस नहीं करना चाहता, लेकिन एक मूल्य। समारोह fooउदाहरण के लिए लग सकता है int foo(){return 1;}:।
12

9
अस्थायी रूप से @aleguna संदर्भ, C ++ 98 की तरह ही आजीवन विस्तार करते हैं।
१२:२२ बजे पर ०५

5
@aleguna आजीवन विस्तार केवल स्थानीय अस्थायी लोगों के साथ काम करता है, न कि संदर्भों को लौटाने के लिए। देखें stackoverflow.com/a/2784304/567292
ecatmur

जवाबों:


232

auto&& var = <initializer>आप का उपयोग करके आप कह रहे हैं: मैं किसी भी इनिशियलाइज़र को स्वीकार करूंगा चाहे वह एक लवल्यू या रिवाल्यू अभिव्यक्ति हो और मैं इसकी कमी को बनाए रखूंगा । यह आमतौर पर अग्रेषण (आमतौर पर T&&) के लिए उपयोग किया जाता है । कारण यह काम करता है क्योंकि एक "सार्वभौमिक संदर्भ" है, auto&&या T&&, किसी भी चीज के लिए बाध्य होगा ।

आप कह सकते हैं, ठीक है कि क्यों न सिर्फ इसका उपयोग किया जाए const auto&क्योंकि यह किसी भी चीज़ से जुड़ेगा? एक 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लूप में है। देखें मेरे दूसरे प्रश्न अधिक जानकारी के लिए।

यदि आप इस तथ्य को संरक्षित करने के लिए std::forwardअपने auto&&संदर्भ का उपयोग करते हैं कि यह मूल रूप से या तो एक लवल्यू या एक प्रतिद्वंद्विता है, तो आपका कोड कहता है: अब जब मुझे आपकी वस्तु या तो एक लवल्यू या रूवल अभिव्यक्ति से मिली है, तो मैं इसे मूल रूप से पूर्णता मूल्य संरक्षित करना चाहता हूं। इसलिए मैंने इसे सबसे कुशलता से उपयोग किया - यह इसे अमान्य कर सकता है। जैसे की:

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खुद को हिम्मत से चीरने की कोशिश नहीं कर सकते - यह बहुत अच्छी तरह से एक अंतराल या यहां तक ​​कि कब्ज हो सकता है। लेकिन हम std::forwardइसे अन्य कार्यों के लिए कर सकते हैं जो पूरी तरह से इसके अंदरूनी हिस्सों को तबाह कर सकते हैं। जैसे ही हम ऐसा करते हैं, हमें varअमान्य स्थिति में होना चाहिए ।

अब इसे इस मामले पर लागू करते हैं auto&& var = 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। हम अनिवार्य रूप get_vectorसे टेम्पलेट पैरामीटर के माध्यम से रिटर्न प्रकार बदलते हैं foo

जब हम कॉल करते हैं foo<std::vector<int>>, get_vectorतो global_vecमूल्य से वापस आ जाएगा , जो एक व्याकुल अभिव्यक्ति देता है। वैकल्पिक रूप से, जब हम कहते हैं foo<std::vector<int>&>, get_vectorवापस आ जाएगी global_vec, संदर्भ द्वारा एक lvalue अभिव्यक्ति हो जाती है।

यदि हम करें तो:

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&&में से किसी को कोड में auto, auto&, 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
}

2
क्या हम केवल T vec = get_vector<T>();फ़ंक्शन फू के अंदर नहीं कह सकते हैं ? या क्या मैं इसे एक बेतुके स्तर पर सरल कर रहा हूँ :)
एस्टरिस्क

@ एस्टरिस्क नो bcoz T vec केवल std के मामले में lvalue को सौंपा जा सकता है :: वेक्टर <int &> और यदि T std :: वेक्टर <int> है तो हम मूल्य के आधार पर कॉल का उपयोग करेंगे जो अक्षम है
Kapit

1
ऑटो और मुझे एक ही परिणाम देता है। मैं MSVC 2015 का उपयोग कर रहा हूं। और GCC एक त्रुटि पैदा करता है।
सेर्गेई पोडोब्री

यहां मैं MSVC 2015 का उपयोग कर रहा हूं, ऑटो और ऑटो और& के समान ही परिणाम देता है।
केहे सीएआई

Int i क्यों है; ऑटो && j = i; अनुमति दी लेकिन int मैं; int && j = i; नहीं है ?
सेवंत्सोन84

14

सबसे पहले, मैं मेरा यह जवाब पढ़ने की सलाह देता हूं कि चरण-दर-चरण स्पष्टीकरण के लिए एक साइड-रीड स्पष्टीकरण के रूप में सार्वभौमिक संदर्भ कार्यों के लिए टेम्पलेट तर्क कटौती कैसे होती है।

क्या इसका मतलब है, हमें संसाधनों की चोरी करने की अनुमति है var?

जरुरी नहीं। क्या होगा अगर foo()अचानक एक संदर्भ वापस आ गया, या आपने कॉल बदल दिया, लेकिन इसके उपयोग को अपडेट करना भूल गया var? या यदि आप जेनेरिक कोड में हैं और रिटर्न का प्रकार foo()आपके मापदंडों के आधार पर बदल सकता है?

के बारे में सोचो auto&&ठीक वही होना करने के लिए T&&में template<class T> void f(T&& v);है, क्योंकि यह है (लगभग ) वास्तव में। जब आप उन्हें पास करने या किसी भी तरह से उपयोग करने की आवश्यकता होती है, तो आप कार्यों में सार्वभौमिक संदर्भों के साथ क्या करते हैं? आप std::forward<T>(v)मूल मूल्य श्रेणी को वापस पाने के लिए उपयोग करते हैं। यदि यह आपके कार्य के लिए पारित होने से पहले एक अंतराल था, तो यह गुजरने के बाद एक अंतराल रहता है std::forward। यदि यह एक प्रतिद्वंद्विता थी, तो यह फिर से एक प्रतिद्वंद्विता बन जाएगी (याद रखें, एक नामांकित संदर्भ एक अंतराल है)।

तो, आप varसामान्य शैली में सही तरीके से कैसे उपयोग करते हैं ? का उपयोग करें std::forward<decltype(var)>(var)। यह ठीक वैसे ही काम करेगा जैसे std::forward<T>(v)ऊपर के फंक्शन टेम्पलेट में। यदि varकोई है T&&, तो आपको वापस एक प्रतिद्वंद्विता मिल जाएगी, और यदि यह है T&, तो आपको एक अंतराल वापस मिलेगा।

इसलिए, विषय पर वापस: क्या करें auto&& v = f();और std::forward<decltype(v)>(v)एक कोडबेस में हमें बताएं? वे हमें बताते हैं कि vअधिग्रहित किया जाएगा और सबसे कुशल तरीके से पारित किया जाएगा। याद रखें, हालांकि, इस तरह के एक चर को अग्रेषित करने के बाद, यह संभव है कि यह स्थानांतरित हो गया है, इसलिए इसे इसे रीसेट किए बिना इसे आगे उपयोग करना गलत होगा।

व्यक्तिगत रूप से, मैं उपयोग करता हूं auto&& जब मैं एक संशोधित चर की आवश्यकता होती है, तो मैं सामान्य कोड में हूं । परफेक्ट-फॉरवर्डिंग एक प्रतिद्वंद्विता को संशोधित कर रहा है, क्योंकि चाल ऑपरेशन संभावित रूप से इसकी हिम्मत चुरा रहा है। अगर मैं सिर्फ आलसी होना चाहता हूं (यानी, यदि मुझे पता है तो भी टाइप नाम न लिखें) और संशोधित करने की आवश्यकता नहीं है (उदाहरण के लिए, जब एक सीमा के तत्वों को मुद्रण करते हैं), तो मैं छड़ी करूंगा auto const&


autoअभी तक अलग है कि एक , whilst auto v = {1,2,3};बना देगाvstd::initializer_listf({1,2,3}) कटौती में विफलता होगी।


आपके उत्तर के पहले भाग में: मेरा मतलब है कि यदि foo()कोई मूल्य-प्रकार लौटाता है T, तो var(यह अभिव्यक्ति) एक अंतराल होगा और इसका प्रकार (इस अभिव्यक्ति का) T(यानी T&&) का एक संदर्भ होगा ।
एमडब्ल्यू

@MWid: समझ में आता है, पहले भाग को हटा दिया।
Xeo

3

कुछ प्रकार पर विचार करें Tजिसमें एक कदम निर्माता है, और मान लें

T t( foo() );

उस मूव कंस्ट्रक्टर का उपयोग करता है।

अब, रिटर्न से कब्जा करने के लिए एक मध्यवर्ती संदर्भ का उपयोग करें foo:

auto const &ref = foo();

यह मूव कंस्ट्रक्टर के उपयोग से संबंधित है, इसलिए रिटर्न वैल्यू को स्थानांतरित करने के बजाय कॉपी करना होगा (यहां तक ​​कि अगर हम std::moveयहां उपयोग करते हैं, तो हम वास्तव में एक कॉस्ट रेफरी के माध्यम से स्थानांतरित नहीं कर सकते हैं)

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

हालांकि, अगर हम उपयोग करते हैं

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

मूव कंस्ट्रक्टर अभी भी उपलब्ध है।


और अपने अन्य प्रश्नों को हल करने के लिए:

... क्या कोई उचित स्थिति है जब आपको अपने कोड के पाठक को कुछ बताने के लिए ऑटो और& का उपयोग करना चाहिए ...

पहली बात, जैसा कि Xeo कहता है, अनिवार्य रूप से मैं X को कुशलतापूर्वक जितना संभव हो उतना ही पास कर रहा हूं , जो भी प्रकार X है। इसलिए, कोड का उपयोग करके देखेंauto&& आंतरिक रूप से यह संवाद करना चाहिए कि यह आंतरिक रूप से जहां उचित हो, चालित शब्दार्थ का उपयोग करेगा।

... जैसे आप करते हैं जब आप एक अनूठे_ptr <> को लौटाने के लिए कहते हैं कि आपके पास विशेष स्वामित्व है ...

जब कोई फ़ंक्शन टेम्प्लेट एक प्रकार का तर्क लेता है T&&, तो यह कह रहा है कि यह आपके द्वारा पास की जाने वाली वस्तु को स्थानांतरित कर सकता है। unique_ptrस्पष्ट रूप से कॉल करने वाले को स्वामित्व देता है; स्वीकार करने से कॉल करने वाले से स्वामित्व हटायाT&& जा सकता है (यदि चाल ctor मौजूद है, आदि)।


2
मुझे यकीन नहीं है कि आपका दूसरा उदाहरण मान्य है। आपको मूव कंस्ट्रक्टर को इनवॉइस करने के लिए सही फॉरवर्ड करने की आवश्यकता नहीं है?

3
ये गलत है। दोनों ही मामलों में प्रति निर्माता, कहा जाता है के बाद से refऔर rvrefदोनों lvalues हैं। अगर आप मूव कंस्ट्रक्टर चाहते हैं, तो आपको लिखना होगा T t(std::move(rvref))
MWid

क्या आप अपने पहले उदाहरण में कॉन्स्ट रेफरी का मतलब auto const &:?
पियोट्रैनिज़

@aleguna - आप और MWID सही हैं, धन्यवाद। मैंने अपना उत्तर तय कर लिया है।
बेकार

1
@ आप सही हैं। लेकिन यह मेरे प्रश्न का उत्तर नहीं देता है। आप कब उपयोग करते हैं auto&&और आप अपने कोड का पाठक को क्या उपयोग करते हैं auto&&?
MWID

-3

auto &&वाक्य रचना सी ++ 11 में से दो नई सुविधाओं का उपयोग करता है:

  1. autoभाग प्रकार संदर्भ (इस मामले में वापसी मान) के आधार पर अनुमान संकलक करने देता है। यह बिना किसी संदर्भ योग्यता के है (आपको यह निर्दिष्ट करने की अनुमति देता है कि आप चाहते हैं T, T &या T &&घटाए गए प्रकार के लिए T)।

  2. &&नई चाल अर्थ विज्ञान है। एक प्रकार का समर्थन करने वाला कदम शब्दार्थ एक रचनाकार को लागू करता T(T && other)है जो सामग्री को नए प्रकार में जानबूझकर स्थानांतरित करता है। यह एक ऑब्जेक्ट को एक गहरी कॉपी करने के बजाय आंतरिक प्रतिनिधित्व को स्वैप करने की अनुमति देता है।

यह आपको कुछ ऐसा करने की अनुमति देता है:

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

इसलिए:

auto var = foo();

लौटे वेक्टर (महंगी) की एक प्रति प्रदर्शन करेंगे, लेकिन:

auto &&var = foo();

वेक्टर के आंतरिक प्रतिनिधित्व को स्वैप करेगा (वेक्टर से fooऔर खाली वेक्टर से var), इसलिए तेज होगा।

इसका उपयोग नए फॉर-लूप सिंटैक्स में किया जाता है:

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

जहां फॉर-लूप auto &&रिटर्न वैल्यू से होल्ड कर रहा है fooऔर itemप्रत्येक वैल्यू का संदर्भ है foo


यह गलत है। auto&&कुछ नहीं ले जाएगा, यह सिर्फ एक संदर्भ बना देगा। चाहे वह लवल्यू हो या रेवल्यू रेफरेंस, इसे इनिशियलाइज़ करने के लिए उपयोग की जाने वाली अभिव्यक्ति पर निर्भर करता है।
जोसेफ मैन्सफील्ड

दोनों मामलों में मूव कंस्ट्रक्टर को कॉल किया जाएगा, चूंकि std::vectorऔर std::stringमूव कंस्ट्रक्टेबल हैं। इस प्रकार के साथ कोई लेना देना नहीं है var
MWID

1
@MWid: वास्तव में, कॉपी / मूव कंस्ट्रक्टर को कॉल भी आरवीओ के साथ पूरी तरह से किया जा सकता है।
मैथ्यू एम।

@MatthieuM। तुम सही हो। लेकिन मुझे लगता है कि उपरोक्त उदाहरण में, कॉपी कनिस्ट्रक्टर को कभी भी कॉल नहीं किया जाएगा, क्योंकि सब कुछ चल-अचल है।
15

1
@MWid: मेरा कहना था कि यहां तक ​​कि मूव कंस्ट्रक्टर को भी खत्म किया जा सकता है। ऐलिस ट्रम्प चलते हैं (यह सस्ता है)।
मैथ्यू एम।
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.