Std :: reference_wrapper और सरल सूचक के बीच अंतर?


100

क्यों होने की आवश्यकता है std::reference_wrapper? इसका उपयोग कहां किया जाना चाहिए? यह एक साधारण पॉइंटर से कैसे अलग है? इसके प्रदर्शन की तुलना एक साधारण सूचक से कैसे की जाती है?


4
यह मूल रूप से एक सूचक है कि आप का उपयोग .करने के बजाय साथ->
एम एम

5
@MM नहीं, .आपके द्वारा सुझाए गए तरीके से काम नहीं करता है (जब तक कि किसी बिंदु पर ऑपरेटर डॉट प्रस्ताव को अपनाया और एकीकृत नहीं किया जाता है :))
कोलंबो

3
यह इस तरह के सवाल हैं जो मुझे दुखी करते हैं जब मुझे नए C ++ के साथ काम करना पड़ता है।
निल्स

Columbo का अनुसरण करने के लिए, std :: reference_wrapper का उपयोग इसके get()सदस्य-फ़ंक्शन के साथ या अंतर्निहित रूप में इसके अंतर्निहित रूपांतरण के साथ किया जाता है ।
मैक्स बैराक्लो

जवाबों:


88

std::reference_wrapperटेम्पलेट्स के साथ संयोजन में उपयोगी है। यह एक पॉइंटर को स्टोर करके एक ऑब्जेक्ट को लपेटता है, इसके सामान्य शब्दार्थ की नकल करते हुए पुन: असाइनमेंट और कॉपी के लिए अनुमति देता है। यह कुछ लाइब्रेरी टेम्प्लेट को ऑब्जेक्ट्स के बजाय संदर्भों को संग्रहीत करने के लिए भी निर्देश देता है।

एसटीएल में एल्गोरिदम पर विचार करें जो कॉपीर को कॉपी करते हैं: आप उस कॉपी को फंक्शनल के बजाय केवल फंक्शनल रेफर करने वाले रेपर से गुजार कर बच सकते हैं:

unsigned arr[10];
std::mt19937 myEngine;
std::generate_n( arr, 10, std::ref(myEngine) ); // Modifies myEngine's state

यह काम करता है क्योंकि ...

  • ... reference_wrapperरों अधिभारoperator() वे सिर्फ समारोह की तरह कहा जा सकता है ताकि वस्तुओं वे का संदर्भ लें:

    std::ref(myEngine)() // Valid expression, modifies myEngines state
  • ... (संयुक्त राष्ट्र) सामान्य संदर्भों की तरह, कॉपी करना (और असाइन करना) reference_wrappersकेवल पॉइंटी असाइन करता है।

    int i, j;
    auto r = std::ref(i); // r refers to i
    r = std::ref(j); // Okay; r refers to j
    r = std::cref(j); // Error: Cannot bind reference_wrapper<int> to <const int>

एक संदर्भ आवरण की नकल करना व्यावहारिक रूप से एक पॉइंटर की नकल करने के बराबर है, जो उतना ही सस्ता है जितना कि यह मिलता है। सभी फ़ंक्शन इसे उपयोग करने में निहित कहते हैं (उदाहरण के लिए operator()) बस इनलाइन होना चाहिए क्योंकि वे एक-लाइनर हैं।

reference_wrappers के माध्यम से बनाया गया है std::refऔरstd::cref :

int i;
auto r = std::ref(i); // r is of type std::reference_wrapper<int>
auto r2 = std::cref(i); // r is of type std::reference_wrapper<const int>

टेम्पलेट तर्क निर्दिष्ट की गई वस्तु के प्रकार और cv- योग्यता को निर्दिष्ट करता है; r2एक संदर्भित करता है const intऔर केवल एक संदर्भ प्राप्त करेगा const intconstउन में फंक्शनलर्स के साथ रेफरर्स को संदर्भित करने के लिए कॉल केवल constसदस्य फ़ंक्शन को कॉल करेगा operator()

Rvalue initializers रोक दिए जाते हैं, क्योंकि उन्हें अनुमति देने से अच्छे से अधिक नुकसान होगा। चूंकि प्रतिद्वंद्वियों को वैसे भी स्थानांतरित किया जाएगा (और गारंटीकृत प्रतिलिपि के साथ यहां तक ​​कि आंशिक रूप से बचा जाता है), हम शब्दार्थ में सुधार नहीं करते हैं; हम झूलने वाले बिंदुओं को पेश कर सकते हैं, हालांकि, एक संदर्भ आवरण सूचक के जीवनकाल का विस्तार नहीं करता है।

लाइब्रेरी इंटरेक्शन

जैसा कि पहले उल्लेख किया गया है, एक make_tupleपरिणामी tupleतर्क को एक के माध्यम से पारित करके एक संदर्भ को संग्रहीत करने का निर्देश दे सकता है reference_wrapper:

int i;
auto t1 = std::make_tuple(i); // Copies i. Type of t1 is tuple<int>
auto t2 = std::make_tuple(std::ref(i)); // Saves a reference to i.
                                        // Type of t2 is tuple<int&>

ध्यान दें कि यह थोड़ा अलग है forward_as_tuple: यहाँ, तर्क के रूप में तर्क की अनुमति नहीं है।

std::bindसमान व्यवहार दिखाता है: यह तर्क की नकल नहीं करेगा, लेकिन अगर यह है तो एक संदर्भ को संग्रहीत करें reference_wrapper। उपयोगी है अगर उस तर्क (या फ़नकार!) की नकल की आवश्यकता नहीं है, लेकिन जब तक bind-functor का उपयोग किया जाता है तब तक गुंजाइश रहती है।

साधारण बिंदुओं से अंतर

  • सिंटैक्टिकल अप्रत्यक्ष का कोई अतिरिक्त स्तर नहीं है। पॉइंटर्स को उनके द्वारा संदर्भित वस्तु के लिए एक अंतराल प्राप्त करने के लिए डीरेफर किया जाना चाहिए; reference_wrappers का एक अंतर्निहित रूपांतरण ऑपरेटर होता है और इसे उस वस्तु की तरह कहा जा सकता है जिसे वे लपेटते हैं।

    int i;
    int& ref = std::ref(i); // Okay
  • reference_wrapperएस, पॉइंटर्स के विपरीत, एक अशक्त अवस्था नहीं है। उन्हें एक संदर्भ या किसी अन्य केreference_wrapper साथ आरंभ किया जाना है ।

    std::reference_wrapper<int> r; // Invalid
  • एक समानता उथले प्रति शब्दार्थ हैं: संकेत और reference_wrapperपुन: असाइन किए जा सकते हैं।


क्या किसी तरह से std::make_tuple(std::ref(i));श्रेष्ठ है std::make_tuple(&i);?
लॉरिनास लाज़ोस्कस

6
@LaurynasLazauskas यह अलग है। आपके द्वारा दिखाया गया उत्तरार्द्ध एक सूचक को बचाता है i, इसका संदर्भ नहीं।
कोलंबो

हम्म ... मुझे लगता है कि मैं अभी भी इन दोनों को अलग नहीं कर सकता, साथ ही मैं चाहूंगा ... ठीक है, धन्यवाद।
लॉरिनास लाजौस्कस

@Columbo संदर्भ आवरणों की एक सरणी कैसे संभव है यदि उनके पास अशक्त अवस्था नहीं है? सरणियाँ आमतौर पर सभी तत्वों के साथ शुरू नहीं होती हैं जो अशक्त अवस्था में हैं?
अनातोलीग

2
@anatolyg उस सरणी को शुरू करने से आपको क्या बाधा है?
कोलंबो

27

कम से कम, दो प्रेरक उद्देश्य हैं std::reference_wrapper<T>:

  1. यह फ़ंक्शन टेम्प्लेट के मान पैरामीटर के रूप में पास की गई वस्तुओं को संदर्भ शब्दार्थ देना है। उदाहरण के लिए, आपके पास एक बड़ी फ़ंक्शन ऑब्जेक्ट हो सकती है जिसे आप पास करना चाहते हैं, std::for_each()जो इसके फ़ंक्शन ऑब्जेक्ट पैरामीटर को मान से लेता है। ऑब्जेक्ट की प्रतिलिपि बनाने से बचने के लिए, आप उपयोग कर सकते हैं

    std::for_each(begin, end, std::ref(fun));

    तर्कों std::reference_wrapper<T>को एक std::bind()अभिव्यक्ति के रूप में पारित करना तर्क को मूल्य के बजाय संदर्भ से बांधने के लिए काफी आम है।

  2. संबंधित टपल तत्व के std::reference_wrapper<T>साथ उपयोग करने पर std::make_tuple()यह एक के T&बजाय एक हो जाता है T:

    T object;
    f(std::make_tuple(1, std::ref(object)));

क्या आप पहले मामले के लिए एक कोड उदाहरण दे सकते हैं?
user1708860

1
@ user1708860: आपका मतलब किसी दिए गए के अलावा है ...?
डाइटमार कुहल

मेरा मतलब है कि वास्तविक कोड जो std :: ref (fun) के साथ जाता है क्योंकि मुझे समझ में नहीं आता है कि इसका उपयोग कैसे किया जाता है (जब तक कि मजाक एक वस्तु नहीं है और फ़ंक्शन नहीं है ...)
user1708860

2
@ user1708860: हाँ, सबसे अधिक संभावना funएक फ़ंक्शन ऑब्जेक्ट है (यानी एक फ़ंक्शन कॉल ऑपरेटर के साथ एक वर्ग की एक वस्तु) और एक फ़ंक्शन नहीं: यदि funवास्तविक फ़ंक्शन होने के लिए, std::ref(fun)कोई उद्देश्य नहीं है और कोड को संभावित रूप से धीमा करना है।
डाइटमार कुहल

23

स्व-दस्तावेजीकरण कोड के संदर्भ में, एक अन्य अंतर यह है कि reference_wrapperवस्तु के स्वामित्व का अनिवार्य रूप से उपयोग किया जाता है। इसके विपरीत, एक unique_ptrस्वामित्व का दावा करता है, जबकि एक नंगे सूचक का स्वामित्व हो सकता है या नहीं हो सकता है (यह संबंधित कोड के बहुत सारे को देखे बिना पता करना संभव नहीं है):

vector<int*> a;                    // the int values might or might not be owned
vector<unique_ptr<int>> b;         // the int values are definitely owned
vector<reference_wrapper<int>> c;  // the int values are definitely not owned

3
जब तक यह प्री-सी ++ 11 कोड नहीं है, तब तक पहला उदाहरण इंडेक्स के आधार पर कैश लुकअप के लिए वैकल्पिक, अज्ञात मानों का उदाहरण देना चाहिए। अच्छा होगा यदि एसटीडी हमें एक गैर-अशक्त, स्वामित्व वाले मूल्य (अद्वितीय और साझा रूप) का प्रतिनिधित्व करने के लिए कुछ मानक प्रदान करता है
Bwmat

यह शायद C ++ 11 में उतना महत्वपूर्ण नहीं है, जहां नंगे पॉइंटर्स लगभग वैसे भी हमेशा उधार मूल्य होंगे।
एलिंग

reference_wrapperकच्चे पॉइंटर्स से बेहतर है क्योंकि न केवल यह स्पष्ट है कि यह गैर-मालिक है, बल्कि इसलिए भी है क्योंकि यह nullptr(शेंनिगन्स के बिना) नहीं हो सकता है और इस प्रकार उपयोगकर्ता जानते हैं कि वे nullptr(बिना शेंनिग के) पास नहीं हो सकते हैं और आपको पता है कि आपको नहीं करना है इसके लिए जाँच करें।
अंडरस्कोर_ड

19

आप इसे संदर्भ के आसपास एक सुविधा आवरण के रूप में सोच सकते हैं ताकि आप उन्हें कंटेनरों में उपयोग कर सकें।

std::vector<std::reference_wrapper<T>> vec; // OK - does what you want
std::vector<T&> vec2; // Nope! Will not compile

यह मूल रूप से का एक CopyAssignableसंस्करण है T&। किसी भी समय आप एक संदर्भ चाहते हैं, लेकिन यह असाइन करने योग्य, उपयोग std::reference_wrapper<T>या इसके सहायक कार्य के लिए है std::ref()। या एक पॉइंटर का उपयोग करें।


अन्य प्रश्न sizeof:

sizeof(std::reference_wrapper<T>) == sizeof(T*) // so 8 on a 64-bit box
sizeof(T&) == sizeof(T) // so, e.g., sizeof(vector<int>&) == 24

और तुलना:

int i = 42;
assert(std::ref(i) == std::ref(i)); // ok

std::string s = "hello";
assert(std::ref(s) == std::ref(s)); // compile error

1
@LaurynasLazauskas रैपर में निहित फ़ंक्शन ऑब्जेक्ट को सीधे कॉल कर सकता है। मेरे उत्तर में यह भी बताया गया है।
कोलंबो

2
चूँकि संदर्भ कार्यान्वयन सिर्फ एक सूचक है जिसके अंदर मैं यह नहीं समझ सकता कि रैपर किसी अप्रत्यक्ष या प्रदर्शन दंड को क्यों जोड़ते हैं
रीगा

4
यह एक साधारण संदर्भ से अधिक अप्रत्यक्ष नहीं होना चाहिए जब यह एक रिलीज कोड की बात आती है
रीगा

3
मुझे उम्मीद है कि संकलक तुच्छ reference_wrapperकोड को इनलाइन करेगा , यह कोड के समान है जो एक सूचक या संदर्भ का उपयोग करता है।
डेविड स्टोन

4
@LaurynasLazauskas: std::reference_wrapperकी गारंटी है कि वस्तु कभी भी अशक्त नहीं है। एक वर्ग के सदस्य पर विचार करें std::vector<T *>। आपको यह देखने के लिए सभी क्लास कोड की जांच करनी होगी कि क्या यह ऑब्जेक्ट कभी भी nullptrवेक्टर में स्टोर हो सकता है , जबकि std::reference_wrapper<T>, आपको मान्य ऑब्जेक्ट होने की गारंटी है।
डेविड स्टोन
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.