"इस" के लिए "संदर्भ क्या है?"


238

एक प्रस्ताव के पार आया, जिसे "क्लैग के सी ++ 11 स्टेटस पेज " में "इस" के लिए रेवल्यू रेफरेंस कहा जाता है ।

मैंने संदर्भों के संदर्भ में काफी कुछ पढ़ा है और उन्हें समझा है, लेकिन मुझे नहीं लगता कि मुझे इस बारे में पता है। मुझे शर्तों का उपयोग करके वेब पर अधिक संसाधन भी नहीं मिले।

पृष्ठ पर प्रस्ताव पेपर के लिए एक लिंक है: N2439 (* यह करने के लिए शब्दार्थ को आगे बढ़ाएं ), लेकिन मुझे वहां से बहुत अधिक उदाहरण नहीं मिल रहे हैं।

इस विशेषता के बारे में क्या है?

जवाबों:


293

सबसे पहले, "यह * के लिए रेफरी-क्वालीफायर" सिर्फ एक "मार्केटिंग स्टेटमेंट" है। *thisकभी नहीं बदलता प्रकार , इस पोस्ट के नीचे देखें। हालांकि इसे इस शब्द के साथ समझना आसान है।

इसके बाद, निम्न कोड चुनता कार्य के आधार पर कहा जा रेफरी-क्वालीफायर समारोह के "अंतर्निहित वस्तु पैरामीटर" का :

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

आउटपुट:

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

पूरी बात आपको इस तथ्य का लाभ उठाने की अनुमति देने के लिए की जाती है जब ऑब्जेक्ट को कॉल किया जाता है, एक प्रतिद्वंद्विता है (उदाहरण के लिए, अस्थायी नाम)। एक और उदाहरण के रूप में निम्नलिखित कोड लें:

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

यह थोड़ा विरोधाभास हो सकता है, लेकिन आपको विचार प्राप्त करना चाहिए।

ध्यान दें कि आप सीवी-क्वालिफायर ( constऔर volatile) और रेफ-क्वालिफायर ( &और &&) को जोड़ सकते हैं ।


नोट: कई मानक उद्धरण और अधिभार संकल्प स्पष्टीकरण यहाँ के बाद!

Ic यह समझने के लिए कि यह कैसे काम करता है, और क्यों @Nicol Bolas का उत्तर कम से कम आंशिक रूप से गलत है, हमें C ++ मानक में थोड़ा सा खोदना होगा (यह समझाने वाला भाग @ निकोल का उत्तर गलत क्यों है? केवल उस में रुचि)।

किस फ़ंक्शन को कहा जा रहा है, इसे अधिभार संकल्प नामक एक प्रक्रिया द्वारा निर्धारित किया जाता है । यह प्रक्रिया काफी जटिल है, इसलिए हम केवल उस बिट को स्पर्श करेंगे जो हमारे लिए महत्वपूर्ण है।

सबसे पहले, यह देखना महत्वपूर्ण है कि सदस्य कार्यों के लिए अधिभार संकल्प कैसे काम करता है:

§13.3.1 [over.match.funcs]

P2 उम्मीदवार के कार्यों के सेट में सदस्य और गैर-सदस्य कार्य दोनों को एक ही तर्क सूची के विरुद्ध हल किया जा सकता है। इसलिए कि इस विषम सेट के भीतर तर्क और पैरामीटर सूचियां तुलनीय हैं, एक सदस्य फ़ंक्शन को एक अतिरिक्त पैरामीटर माना जाता है, जिसे अंतर्निहित ऑब्जेक्ट पैरामीटर कहा जाता है, जो उस वस्तु का प्रतिनिधित्व करता है जिसके लिए सदस्य फ़ंक्शन को बुलाया गया है । [...]

p3 इसी प्रकार, उपयुक्त होने पर, संदर्भ एक तर्क सूची का निर्माण कर सकता है जिसमें ऑब्जेक्ट को संचालित करने के लिए निरूपित करने के लिए एक निहित ऑब्जेक्ट तर्क होता है।

हमें सदस्य और गैर-सदस्य कार्यों की तुलना करने की आवश्यकता क्यों है? ऑपरेटर ओवरलोडिंग, इसीलिए। इस पर विचार करो:

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

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

आप निश्चित रूप से नि: शुल्क फ़ंक्शन को कॉल करने के लिए चाहते हैं, क्या आप नहीं?

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

यही कारण है कि सदस्य और गैर-सदस्य फ़ंक्शन तथाकथित ओवरलोड-सेट में शामिल हैं। संकल्प को कम जटिल बनाने के लिए, मानक उद्धरण का बोल्ड हिस्सा मौजूद है। इसके अतिरिक्त, यह हमारे लिए महत्वपूर्ण बिट है (एक ही खंड):

p4 गैर-स्थैतिक सदस्य फ़ंक्शन के लिए, अंतर्निहित ऑब्जेक्ट पैरामीटर का प्रकार है

  • "करने के लिए lvalue संदर्भ सीवी X कार्यों के लिए" एक के बिना घोषित रेफरी-क्वालीफायर या साथ & रेफरी-क्वालीफायर

  • रेफरी-क्वालीफायर के साथ घोषित किए गए कार्यों के लिए " सीवी के लिए संदर्भ X"&&

Xवह वर्ग कहाँ है जो फ़ंक्शन का सदस्य है और cv सदस्य फ़ंक्शन की घोषणा पर cv-योग्यता है। [...]

p5 अधिभार के प्रस्ताव के दौरान [...] [t] वह ऑब्जेक्ट पैरामीटर को निहित करता है [...] इसी पहचान को बनाए रखता है क्योंकि संबंधित तर्क पर रूपांतरण इन अतिरिक्त नियमों का पालन करेगा:

  • अंतर्निहित वस्तु पैरामीटर के लिए तर्क रखने के लिए कोई अस्थायी वस्तु पेश नहीं की जा सकती; तथा

  • इसके साथ एक प्रकार का मिलान प्राप्त करने के लिए कोई उपयोगकर्ता-परिभाषित रूपांतरण लागू नहीं किया जा सकता है

[...]

(अंतिम बिट का अर्थ है कि आप सदस्य के फ़ंक्शन (या ऑपरेटर को कॉल किया जाता है) के निहित रूपांतरण के आधार पर अधिभार संकल्प को धोखा नहीं दे सकते।)

आइए इस पोस्ट के शीर्ष पर पहला उदाहरण लेते हैं। उपर्युक्त परिवर्तन के बाद, अधिभार-सेट कुछ इस तरह दिखता है:

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

तब तर्क सूची, जिसमें एक निहित ऑब्जेक्ट तर्क होता है , को अधिभार-सेट में निहित प्रत्येक फ़ंक्शन के पैरामीटर-सूची के विरुद्ध मिलान किया जाता है। हमारे मामले में, तर्क सूची में केवल वही ऑब्जेक्ट तर्क होगा। आइए देखें कि यह कैसा दिखता है:

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

यदि, सेट में सभी अधिभार के बाद परीक्षण किया जाता है, तो केवल एक ही शेष रहता है, अधिभार संकल्प सफल हुआ और उस परिवर्तित अधिभार से जुड़े कार्य को कहा जाता है। वही दूसरी कॉल के लिए 'f' जाता है:

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

नोट तथापि कि, हम किसी भी मुहैया कराई थी नहीं रेफरी-क्वालीफायर (और इस तरह के रूप में कार्य ओवरलोड न हो), कि f1 होगा एक rvalue (अभी भी मेल खाते हैं §13.3.1):

p5 [...] गैर-स्थैतिक सदस्य कार्यों के लिए रेफरी-क्वालीफायर के बिना घोषित , एक अतिरिक्त नियम लागू होता है:

  • भले ही अंतर्निहित ऑब्जेक्ट पैरामीटर const-क्वालिफ़ाइड नहीं है , फिर भी एक प्रतिद्वंद्विता पैरामीटर के लिए बाध्य हो सकती है जब तक कि अन्य सभी मामलों में तर्क को अंतर्निहित ऑब्जेक्ट पैरामीटर के प्रकार में परिवर्तित किया जा सकता है।
struct test{
  void f() { std::cout << "lvalue or rvalue object\n"; }
};

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

अब, क्यों @ निकोल का जवाब कम से कम गलत है। वह कहता है:

ध्यान दें कि यह घोषणा प्रकार बदलता है *this

यह गलत है, *thisहै हमेशा एक lvalue:

§5.3.1 [expr.unary.op] p1

यूनिरी *ऑपरेटर अप्रत्यक्ष प्रदर्शन करता है : जिस अभिव्यक्ति पर इसे लागू किया जाता है वह एक ऑब्जेक्ट प्रकार के लिए एक संकेतक होगा, या एक फ़ंक्शन प्रकार के लिए एक संकेतक होगा और परिणाम वस्तु या फ़ंक्शन का एक अंतराल है जो अभिव्यक्ति को इंगित करता है।

§9.3.2 [class.this] p1

एक गैर-स्थिर (9.3) सदस्य फ़ंक्शन के शरीर में, कीवर्ड thisएक प्रचलित अभिव्यक्ति है जिसका मूल्य उस ऑब्जेक्ट का पता है जिसके लिए फ़ंक्शन कहा जाता है। के प्रकार के thisएक वर्ग के एक सदस्य समारोह में Xहै X*। [...]


मेरा मानना ​​है कि "परिवर्तन के बाद" अनुभाग के बाद पैरान्टर प्रकार 'परीक्षण' के बजाय 'फू' होना चाहिए।
5

@ आर्यन: अच्छा लगा, धन्यवाद। हालांकि पैरामीटर नहीं है लेकिन कार्यों का वर्ग पहचानकर्ता गलत है। :)
Xeo

उफ़ खेद है कि मैं उस टेस्ट नाम की टॉय क्लास के बारे में भूल गया, जब मैंने उस हिस्से को पढ़ा और सोचा कि फू इस तरह से मेरी टिप्पणी के भीतर समाहित है ..
ryaner

क्या यह निर्माणकर्ताओं के साथ किया जा सकता है MyType(int a, double b) &&:?
जर्मेन डिआगो

2
"* का प्रकार यह कभी नहीं बदलता है" आपको शायद थोड़ा स्पष्ट होना चाहिए कि यह आर / एल-मूल्य योग्यता के आधार पर नहीं बदलता है। लेकिन यह const / non-const के बीच बदल सकता है।
xxxon

78

लैवल्यू रेफ-क्वालिफायर फॉर्म के लिए एक अतिरिक्त उपयोग मामला है। C ++ 98 में ऐसी भाषा है जो गैर- constसदस्य कार्यों को वर्ग उदाहरणों के लिए बुलाया जा सकता है जो कि तर्क हैं। यह उन सभी प्रकार की विचित्रताओं की ओर जाता है जो व्याप्ति की अवधारणा के विरुद्ध है और कार्य के प्रकारों से विचलित होता है:

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-Qualifiers इन समस्याओं को हल करते हैं:

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

अब ऑपरेटर्स बिलिन प्रकारों को स्वीकार करते हैं, केवल अंतराल को स्वीकार करते हुए।


28

मान लें कि आपके पास एक वर्ग पर दो कार्य हैं, दोनों समान नाम और हस्ताक्षर के साथ। लेकिन उनमें से एक घोषित किया गया है const:

void SomeFunc() const;
void SomeFunc();

यदि कोई श्रेणी उदाहरण नहीं है const, तो अधिभार संकल्प अधिमानतः गैर-कॉन्स्टेबल संस्करण का चयन करेगा। यदि उदाहरण है const, तो उपयोगकर्ता केवल constसंस्करण को कॉल कर सकता है । और thisसूचक एक constसूचक है, इसलिए उदाहरण को बदला नहीं जा सकता है।

इस "के लिए" r- मान संदर्भ क्या आपको एक और विकल्प जोड़ने की अनुमति देता है:

void RValueFunc() &&;

यह आपको एक फ़ंक्शन करने की अनुमति देता है जिसे केवल तभी कहा जा सकता है जब उपयोगकर्ता इसे उचित आर-मूल्य के माध्यम से कहता है। तो अगर यह प्रकार में है Object:

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

इस तरह, आप इस बात पर आधारित व्यवहार कर सकते हैं कि ऑब्जेक्ट को r-value के माध्यम से एक्सेस किया जा रहा है या नहीं।

ध्यान दें कि आपको r- मूल्य संदर्भ संस्करणों और गैर-संदर्भ संस्करणों के बीच अधिभार की अनुमति नहीं है। यही है, यदि आपके पास एक सदस्य फ़ंक्शन नाम है, तो इसके सभी संस्करण या तो एल / आर-वैल्यू क्वालिफायर का उपयोग करते हैं this, या उनमें से कोई भी नहीं करता है। आप ऐसा नहीं कर सकते:

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

आपको यह करना चाहिए:

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

ध्यान दें कि यह घोषणा किस प्रकार का परिवर्तन करती है *this। इसका मतलब यह है कि &&संस्करण सभी सदस्यों को आर-मूल्य संदर्भ के रूप में एक्सेस करते हैं। तो यह आसानी से वस्तु के भीतर से स्थानांतरित करना संभव हो जाता है। प्रस्ताव के पहले संस्करण में दिया गया उदाहरण है (ध्यान दें: निम्नलिखित C ++ 11 के अंतिम संस्करण के साथ सही नहीं हो सकता; यह इस "प्रस्ताव से प्रारंभिक" r- मूल्य से सीधा है):

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
मुझे लगता है कि आपको std::moveदूसरे संस्करण की आवश्यकता है , गैर? इसके अलावा, क्यों रैवल्यू संदर्भ वापसी?
Xeo

1
@ Xeo: क्योंकि प्रस्ताव में उदाहरण क्या था; मुझे इसका कोई अंदाजा नहीं है अगर यह अभी भी वर्तमान संस्करण के साथ काम करता है। और आर-मूल्य संदर्भ वापसी का कारण यह है कि आंदोलन को कैप्चर करने वाले व्यक्ति तक होना चाहिए। यह अभी तक नहीं होना चाहिए, बस अगर वह वास्तव में एक मान के बजाय इसे && में संग्रहीत करना चाहता है।
निकोल बोलस

3
सही, मैं थोड़े अपने दूसरे सवाल का कारण सोचा। मुझे आश्चर्य है, हालांकि, एक अस्थायी के सदस्य के जीवनकाल को उस अस्थायी, या उसके सदस्य के जीवनकाल को संदर्भित करता है? मैं कसम खा सकता हूं कि मैंने कुछ समय पहले एसओ के बारे में एक सवाल देखा था ...
Xeo

1
@ Xeo: यह पूरी तरह सच नहीं है। यदि यह मौजूद है तो ओवरलोड रिज़ॉल्यूशन हमेशा नॉन-कास्ट संस्करण को चुनेगा। कास्ट संस्करण प्राप्त करने के लिए आपको एक कास्ट करने की आवश्यकता होगी। मैंने स्पष्ट करने के लिए पोस्ट अपडेट की है।
निकोल बोलस

15
मैंने सोचा कि मैं समझा सकता हूं, आखिरकार मैंने C ++ 11 के लिए यह सुविधा बनाई;) Xeo जोर देकर कह रहा है कि यह किस प्रकार का परिवर्तन नहीं करता है *this, हालांकि मैं समझ सकता हूं कि भ्रम कहां से आता है। ऐसा इसलिए है क्योंकि रेफ-क्वालिफायर निहित (या "छिपा हुआ") फ़ंक्शन पैरामीटर के प्रकार को बदल देता है, जिसके लिए "यह" (यहां उद्देश्य पर उद्धरण!) ऑब्जेक्ट ओवरलोड रिज़ॉल्यूशन और फ़ंक्शन कॉल के दौरान बाध्य है। इसलिए, *thisचूंकि Xeo के बारे में बताया गया है , इसलिए इसमें कोई बदलाव नहीं किया गया है। "Hiddden" पैरामीटर को बदलने के बजाय इसे lvalue- या rvalue-reference बनाने के लिए, जैसे constफंक्शन क्वालिफायर इसे बनाते हैं constआदि
bronekk
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.