मुझे इस पॉइंटर के माध्यम से टेम्प्लेट बेस क्लास सदस्यों तक क्यों पहुंचना है?


199

यदि नीचे की कक्षाएं टेम्प्लेट नहीं थीं, तो मैं बस क्लास xमें हो सकता था derived। हालांकि, नीचे दिए गए कोड के साथ, मैं करने के लिए है उपयोग this->x। क्यों?

template <typename T>
class base {

protected:
    int x;
};

template <typename T>
class derived : public base<T> {

public:
    int f() { return this->x; }
};

int main() {
    derived<int> d;
    d.f();
    return 0;
}

1
आह जीजा। यह नाम देखने के साथ कुछ करना है। अगर कोई जल्द ही इसका जवाब नहीं देता है, तो मैं इसे देखूंगा और इसे पोस्ट करूंगा (अभी व्यस्त हूं)।
templatetypedef

@ एड स्वांगरेन: क्षमा करें, मैंने इस प्रश्न को पोस्ट करते समय प्रस्तावित उत्तरों के बीच इसे याद किया। मैं इससे पहले लंबे समय से जवाब की तलाश में था।
अली

6
यह दो-चरण नाम लुकअप (जो सभी संकलक डिफ़ॉल्ट रूप से उपयोग नहीं करते हैं) और आश्रित नामों के कारण होता है। इस समस्या के 3 समाधान हैं, xजिनके साथ उपसर्ग करने के अलावा this->, अर्थात्: 1) उपसर्ग का उपयोग करें base<T>::x, 2) एक बयान जोड़ें using base<T>::x, 3) एक वैश्विक संकलक स्विच का उपयोग करें जो अनुमेय मोड को सक्षम करता है। इन समाधानों के पेशेवरों और विपक्षों का वर्णन stackoverflow.com/questions/50321788/…
जॉर्ज रॉबिन्सन

जवाबों:


274

संक्षिप्त जवाब: xएक आश्रित नाम बनाने के लिए , ताकि लुकअप टाल दिया जाए जब तक कि टेम्प्लेट पैरामीटर ज्ञात न हो जाए।

दीर्घ उत्तर: जब कोई कंपाइलर कोई टेम्प्लेट देखता है, तो उसे टेम्प्लेट पैरामीटर को देखे बिना, तुरंत कुछ जाँच करना माना जाता है। पैरामीटर ज्ञात होने तक अन्य को स्थगित कर दिया जाता है। इसे दो-चरण संकलन कहा जाता है, और MSVC ऐसा नहीं करता है, लेकिन यह मानक द्वारा आवश्यक है और अन्य प्रमुख संकलक द्वारा कार्यान्वित किया जाता है। यदि आप चाहें, तो कंपाइलर को जैसे ही यह दिखाई देता है (किसी प्रकार का आंतरिक पार्स ट्री प्रतिनिधित्व) को संकलित करना चाहिए, और बाद में तुरंत संकलित करना स्थगित करना चाहिए।

टेम्पलेट पर किए जाने वाले चेक, विशेष रूप से इसके त्वरित इंस्ट्रूमेंटेशन के बजाय, यह आवश्यक है कि कंपाइलर टेम्पलेट के कोड के व्याकरण को हल करने में सक्षम हो।

C ++ (और C) में, कोड के व्याकरण को हल करने के लिए, आपको कभी-कभी यह जानना होगा कि कुछ एक प्रकार है या नहीं। उदाहरण के लिए:

#if WANT_POINTER
    typedef int A;
#else
    int A;
#endif
static const int x = 2;
template <typename T> void foo() { A *x = 0; }

यदि A एक प्रकार है, जो एक पॉइंटर घोषित करता है (वैश्विक छाया के अलावा कोई प्रभाव नहीं है x)। यदि A एक ऑब्जेक्ट है, तो यह गुणा है (और कुछ ऑपरेटर को अवैध रूप से ओवरलोड करने पर रोक लगाता है, जो एक प्रतिद्वंद्विता को असाइन करता है)। यदि यह गलत है, तो इस त्रुटि का चरण 1 में निदान किया जाना चाहिए , यह मानक द्वारा टेम्पलेट में एक त्रुटि होने के लिए परिभाषित किया गया है , न कि इसके कुछ विशेष तात्कालिकता में। भले ही टेम्प्लेट को कभी भी त्वरित नहीं किया जाता है, अगर ए intतो एक उपरोक्त कोड बीमार है और इसका निदान किया जाना चाहिए, जैसे कि अगर यह बिल्कुल fooटेम्पलेट नहीं था, लेकिन एक सादे कार्य।

अब, मानक का कहना है कि वे नाम जो टेम्प्लेट पैरामीटर पर निर्भर नहीं हैं , उन्हें चरण 1 में फिर से शुरू करने योग्य होना चाहिए। Aयहाँ एक निर्भर नाम नहीं है, यह एक ही चीज़ को संदर्भित करता है, भले ही प्रकार का हो T। इसलिए इसे चरण 1 में ढूंढने और जांचने के लिए टेम्पलेट को परिभाषित करने से पहले परिभाषित किया जाना चाहिए।

T::Aएक ऐसा नाम होगा जो T पर निर्भर करता है। हम संभवतः चरण 1 में नहीं जान सकते कि यह एक प्रकार है या नहीं। प्रकार जो अंततः Tएक तात्कालिकता के रूप में उपयोग किया जाएगा काफी संभावना अभी भी परिभाषित नहीं किया गया है, और यहां तक ​​कि अगर हम यह नहीं जानते थे कि कौन सा प्रकार (ओं) को हमारे टेम्पलेट पैरामीटर के रूप में उपयोग किया जाएगा। लेकिन हमें व्याकरण को हल करना होगा ताकि हमारे अनमोल चरण 1 की जांच बीमार लोगों के लिए हो सके। अतः मानक पर निर्भर नामों के लिए एक नियम है - संकलक को यह मान लेना चाहिए कि वे गैर-प्रकार के हैं, जब तक typenameकि यह निर्दिष्ट करने के लिए योग्य नहीं है कि वे टाइप हैं, या कुछ असंदिग्ध संदर्भों में उपयोग किए जाते हैं । उदाहरण के लिए template <typename T> struct Foo : T::A {};,T::A बेस क्लास के रूप में उपयोग किया जाता है और इसलिए यह एक प्रकार से स्पष्ट रूप से है। यदि Fooकिसी प्रकार के साथ तात्कालिकता है जिसमें एक डेटा सदस्य हैA नेस्टेड प्रकार ए के बजाय, यह कोड में एक त्रुटि है तात्कालिकता (चरण 2), न कि टेम्पलेट (चरण 2) में त्रुटि।

लेकिन आश्रित आधार वर्ग के साथ वर्ग टेम्पलेट के बारे में क्या?

template <typename T>
struct Foo : Bar<T> {
    Foo() { A *x = 0; }
};

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

typename std::string s = "hello, world";

क्योंकि std::stringएक आश्रित नाम होगा, और इसलिए एक गैर-प्रकार माना जाता है जब तक कि अन्यथा निर्दिष्ट न हो। आउच!

आपके पसंदीदा कोड ( return x;) की अनुमति देने के साथ एक दूसरी समस्या यह है कि भले ही Barपहले परिभाषित किया गया हो Foo, और xउस परिभाषा में कोई सदस्य नहीं है, कोई बाद में Barकिसी प्रकार के लिए एक विशेषज्ञता को परिभाषित कर सकता है Baz, Bar<Baz>जिसमें डेटा सदस्य हो x, और फिर तुरंत Foo<Baz>। तो उस तात्कालिकता में, आपका खाका वैश्विक वापस करने के बजाय डेटा सदस्य को वापस कर देगा x। या इसके विपरीत अगर बेस टेम्प्लेट की परिभाषा Barथी x, तो वे इसके बिना एक विशेषज्ञता को परिभाषित कर सकते थे, और आपका टेम्प्लेट xवापस लौटने के लिए एक वैश्विक की तलाश करेगा Foo<Baz>। मुझे लगता है कि यह आपके लिए समस्या के रूप में आश्चर्यजनक और चिंताजनक था, लेकिन यह चुपचाप है आश्चर्यजनक, के रूप में एक आश्चर्यजनक त्रुटि फेंकने का विरोध किया।

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

  • using Bar<T>::A;कक्षा में - Aअब कुछ में संदर्भित करता है Bar<T>, इसलिए निर्भर है।
  • Bar<T>::A *x = 0;उपयोग के बिंदु पर - फिर, Aनिश्चित रूप से अंदर है Bar<T>। यह गुणा typenameका उपयोग नहीं किया गया था, इसलिए संभवतः एक बुरा उदाहरण है, लेकिन हमें यह पता लगाने के लिए कि जब तक operator*(Bar<T>::A, x)कोई प्रतिफल नहीं मिल जाता है, तब तक इंतजार करना होगा । कौन जानता है, शायद यह करता है ...
  • this->A;उपयोग के बिंदु पर - Aएक सदस्य है, इसलिए यदि यह अंदर नहीं है Foo, तो यह आधार वर्ग में होना चाहिए, फिर से मानक कहता है कि यह निर्भर करता है।

दो-चरण का संकलन फिडली और कठिन है, और आपके कोड में अतिरिक्त वर्बेज के लिए कुछ आश्चर्यजनक आवश्यकताओं का परिचय देता है। लेकिन लोकतंत्र की तरह यह संभवत: अन्य सभी के अलावा चीजों को करने का सबसे खराब तरीका है।

आप यथोचित तर्क दे सकते हैं कि आपके उदाहरण में, return x;यह समझ में नहीं आता है कि xक्या आधार वर्ग में एक नेस्टेड प्रकार है, इसलिए भाषा को (ए) कहना चाहिए कि यह एक आश्रित नाम है और (2) इसे एक गैर-प्रकार के रूप में मानते हैं, और आपका कोड बिना काम करेगा this->। एक हद तक आप समस्या के समाधान से संपार्श्विक क्षति का शिकार होते हैं जो आपके मामले में लागू नहीं होता है, लेकिन अभी भी आपके आधार वर्ग का मुद्दा संभावित रूप से आपके नीचे उन नामों को प्रस्तुत कर रहा है जो छाया ग्लोबल्स हैं, या आपके द्वारा सोचे गए नाम नहीं हैं। उनके पास था, और एक वैश्विक बजाय पाया जा रहा है।

आप संभवतः यह भी तर्क दे सकते हैं कि डिफ़ॉल्ट निर्भर नामों के लिए विपरीत होना चाहिए (मान लें कि जब तक किसी वस्तु को निर्दिष्ट नहीं किया जाता है तब तक टाइप करें), या यह कि डिफ़ॉल्ट अधिक संदर्भ संवेदनशील होना चाहिए (में std::string s = "";, std::stringएक प्रकार के रूप में पढ़ा जा सकता है क्योंकि कुछ भी व्याकरणिक नहीं बनाता है) भावना, भले ही std::string *s = 0;अस्पष्ट है)। फिर, मुझे नहीं पता कि नियम कैसे सहमत हुए। मेरा अनुमान है कि पाठ के उन पृष्ठों की संख्या की आवश्यकता होगी, जिनके लिए बहुत से विशिष्ट नियमों को बनाने के खिलाफ कम किया गया है, जिनके लिए संदर्भ एक प्रकार का है और जो एक गैर-प्रकार है।


1
ऊह, अच्छा विस्तृत जवाब। कुछ चीजों को स्पष्ट किया, जिन्हें मैंने कभी देखने की जहमत नहीं उठाई। :) +1
जलफ

20
@jalf: क्या C ++ QTWBFAETYNSYEWTKTAAHMITTBGOW जैसी कोई चीज़ है - "ऐसे प्रश्न जो अक्सर पूछे जाएंगे, सिवाय इसके कि आप जवाब जानना भी नहीं चाहते और आपके साथ होने वाली और भी बहुत सारी चीज़ें हैं"?
स्टीव जेसप

4
असाधारण जवाब, आश्चर्य है कि अगर सवाल faq में फिट हो सकता है।
मैथ्यू एम।

वाह, क्या हम विश्वकोश कह सकते हैं? हाईफाइव वन सूक्ष्म बिंदु, हालांकि: "अगर फू कुछ प्रकार के साथ त्वरित है , जिसमें एक नेस्टेड प्रकार ए के बजाय एक डेटा सदस्य ए है, तो कोड में एक त्रुटि है तात्कालिकता (चरण 2), टेम्पलेट में त्रुटि नहीं (चरण) 1)। " यह कहना बेहतर होगा कि टेम्प्लेट विकृत नहीं है, लेकिन फिर भी यह टेम्प्लेट लेखक की ओर से एक गलत धारणा या तर्क बग का मामला हो सकता है। यदि ध्वजांकित तात्कालिकता वास्तव में इच्छित usecase थी, तो टेम्पलेट गलत होगा।
आयनोकॉस्ट ब्रिघम

1
@JohnH। यह देखते हुए कि कई संकलक कार्यान्वित -fpermissiveया समान हैं, हाँ यह संभव है। मुझे इस बात का विवरण नहीं है कि यह कैसे लागू किया गया है, लेकिन संकलक को xतब तक निराकरण करना चाहिए जब तक वह वास्तविक अस्थायी आधार वर्ग को नहीं जानता T। अतः, गैर-अनुमेय मोड में सिद्धांत रूप में यह इस तथ्य को रिकॉर्ड कर सकता है कि उसने इसे टाल दिया है, इसे स्थगित कर दें, एक बार इसे देखने के बाद लुकअप कर लें T, और जब लुकअप सफल होने वाले पाठ को जारी करने में मदद करता है। यह एक बहुत ही सटीक सुझाव होगा यदि यह केवल उन मामलों में किया जाता है जहां यह काम करता है: उपयोगकर्ता का मतलब कुछ अन्य xसे अभी तक एक और गुंजाइश है बहुत छोटे हैं!
स्टीव जेसोप

13

(मूल जवाब 10 जनवरी, 2011 से)

मुझे लगता है कि मुझे जवाब मिल गया है: जीसीसी मुद्दा: एक बेस क्लास के सदस्य का उपयोग करना जो टेम्पलेट तर्क पर निर्भर करता है । उत्तर जीसीसी के लिए विशिष्ट नहीं है।


अद्यतन: के जवाब में mmichael की टिप्पणी , से मसौदा N3337 सी ++ 11 स्टैंडर्ड की:

14.6.2 आश्रित नाम [temp.dep]
[...]
3 किसी वर्ग या वर्ग टेम्पलेट की परिभाषा में, यदि कोई आधार वर्ग टेम्पलेट-पैरामीटर पर निर्भर करता है, तो बेस क्लास स्कोप को अयोग्य नाम लुकअप के दौरान या तो परखा नहीं जाता है वर्ग टेम्पलेट या सदस्य की परिभाषा या वर्ग टेम्पलेट या सदस्य की तात्कालिकता के दौरान।

क्या "क्योंकि मानक ऐसा कहता है" उत्तर के रूप में गिना जाता है, मुझे नहीं पता। अब हम पूछ सकते हैं कि मानक क्यों अनिवार्य है, लेकिन जैसा कि स्टीव जेसोप का उत्कृष्ट उत्तर और अन्य बताते हैं, इस उत्तरार्द्ध प्रश्न का उत्तर लंबा और तर्कपूर्ण है। दुर्भाग्य से, जब यह सी ++ मानक की बात आती है, तो अक्सर यह असंभव होता है कि लघु और स्व-निहित स्पष्टीकरण दिया जाए कि मानक कुछ क्यों करता है; यह बाद के प्रश्न पर भी लागू होता है।


11

xविरासत के दौरान छिपा हुआ है। आप के माध्यम से unhide कर सकते हैं:

template <typename T>
class derived : public base<T> {

public:
    using base<T>::x;             // added "using" statement
    int f() { return x; }
};

25
यह उत्तर स्पष्ट नहीं करता है कि यह क्यों छिपा हुआ है।
jamesdlin
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.