ऑपरेटर द्वारा ओवरलोड होने पर मैं किसी वस्तु का पता कैसे प्राप्त कर सकता हूं?


170

निम्नलिखित कार्यक्रम पर विचार करें:

struct ghost
{
    // ghosts like to pretend that they don't exist
    ghost* operator&() const volatile { return 0; }
};

int main()
{
    ghost clyde;
    ghost* clydes_address = &clyde; // darn; that's not clyde's address :'( 
}

मुझे clydeपता कैसे मिलेगा ?

मैं एक ऐसे समाधान की तलाश में हूं जो सभी प्रकार की वस्तुओं के लिए समान रूप से अच्छी तरह से काम करेगा। एक C ++ 03 समाधान अच्छा होगा, लेकिन मुझे C ++ 11 समाधानों में भी दिलचस्पी है। यदि संभव हो, तो किसी भी कार्यान्वयन-विशिष्ट व्यवहार से बचें।

मुझे C ++ 11 के std::addressofफ़ंक्शन टेम्पलेट के बारे में पता है , लेकिन यहां इसका उपयोग करने में कोई दिलचस्पी नहीं है: मैं यह समझना चाहता हूं कि एक मानक लाइब्रेरी कार्यान्वयनकर्ता इस फ़ंक्शन टेम्पलेट को कैसे लागू कर सकता है।


41
@ जैलफ: यह रणनीति स्वीकार्य है, लेकिन अब जब मैंने कहा है कि व्यक्तियों को सिर में मुक्का मारा जाता है, तो मैं उनके घृणित कोड के आसपास कैसे काम करूं? :-)
जेम्स मैकनेलिस

5
@jalf Uhm, कभी-कभी आपको इस ऑपरेटर को अधिभारित करने की आवश्यकता होती है, और एक प्रॉक्सी ऑब्जेक्ट लौटाता है। हालाँकि मैं अभी एक उदाहरण के बारे में नहीं सोच सकता।
कोनराड रुडोल्फ

5
@Konrad: मुझे या तो। यदि आपको इसकी आवश्यकता है, तो मेरा सुझाव है कि एक बेहतर विकल्प आपके डिज़ाइन पर पुनर्विचार करना हो सकता है, क्योंकि उस ऑपरेटर को ओवरलोड करने से बस बहुत अधिक समस्याएं होती हैं। :)
जल्लफ

2
@ कोनराड: C ++ प्रोग्रामिंग के लगभग 20 वर्षों में मैंने एक बार उस ऑपरेटर को अधिभारित करने का प्रयास किया है। यह उन बीस वर्षों की शुरुआत में था। ओह, और मैं उस प्रयोग को सफल बनाने में असफल रहा। नतीजतन, अकसर पूछे जाने वाले प्रश्न के लिए ओवरलोडिंग करने वाले ऑपरेटर कहते हैं, " ऑपरेटर का एकतरफा पता कभी भी ओवरलोड नहीं होना चाहिए।" अगली बार जब हम मिलेंगे तो आपको एक मुफ्त बीयर मिलेगी यदि आप इस ऑपरेटर को ओवरलोड करने के लिए एक ठोस उदाहरण के साथ आ सकते हैं। (मुझे पता है कि आप बर्लिन छोड़ रहे हैं, इसलिए मैं इसे सुरक्षित रूप से पेश कर सकता हूं :))
sbi

5
CComPtr<>और CComQIPtr<>एक ओवरलोडेड हैoperator&
साइमन रिक्टर

जवाबों:


102

अपडेट: C ++ 11 में, std::addressofइसके बजाय कोई भी उपयोग कर सकता है boost::addressof


आइए पहले हम बूस्ट से कोड कॉपी करते हैं, बिट्स के आसपास कंपाइलर काम करते हैं:

template<class T>
struct addr_impl_ref
{
  T & v_;

  inline addr_impl_ref( T & v ): v_( v ) {}
  inline operator T& () const { return v_; }

private:
  addr_impl_ref & operator=(const addr_impl_ref &);
};

template<class T>
struct addressof_impl
{
  static inline T * f( T & v, long ) {
    return reinterpret_cast<T*>(
        &const_cast<char&>(reinterpret_cast<const volatile char &>(v)));
  }

  static inline T * f( T * v, int ) { return v; }
};

template<class T>
T * addressof( T & v ) {
  return addressof_impl<T>::f( addr_impl_ref<T>( v ), 0 );
}

यदि हम फ़ंक्शन का संदर्भ देते हैं तो क्या होता है ?

नोट: addressofकार्य करने के लिए एक सूचक के साथ उपयोग नहीं किया जा सकता है

C ++ में अगर void func();घोषित किया गया है, तो funcएक फ़ंक्शन का संदर्भ है जिसमें कोई तर्क नहीं है और कोई परिणाम नहीं लौटाता है। एक फ़ंक्शन का यह संदर्भ तुच्छ रूप से कार्य करने के लिए एक पॉइंटर में बदल सकता है - से @Konstantin: 13.3.3.2 के अनुसार दोनों T &औरT * कार्यों के लिए अप्रभेद्य हैं। पहला एक पहचान रूपांतरण है और दूसरा एक "सटीक मिलान" रैंक (13.3.3.1.1 तालिका 9) वाले फ़ंक्शन-टू-पॉइंटर रूपांतरण है।

समारोह के संदर्भ के माध्यम से पारित addr_impl_ref, के चुनाव के लिए अधिभार संकल्प में एक अस्पष्टता नहीं है f, जो डमी तर्क करने के लिए धन्यवाद हल किया जाता है 0, जो एक है intपहली और एक करने के लिए प्रोत्साहित किया जा सकता है long(इंटीग्रल रूपांतरण)।

इस प्रकार हम केवल पॉइंटर लौटाते हैं।

यदि हम रूपांतरण ऑपरेटर के साथ एक प्रकार पास करते हैं तो क्या होगा?

यदि रूपांतरण ऑपरेटर पैदावार करता है T*तो हमारे पास एक अस्पष्टता है: f(T&,long)दूसरे तर्क के लिए एक इंटीग्रल प्रमोशन की आवश्यकता होती है जबकि f(T*,int)रूपांतरण ऑपरेटर को पहली बार बुलाया जाता है (@litb के लिए धन्यवाद)

ऐसा तब होता है जब addr_impl_refC ++ मानक कहता है कि रूपांतरण अनुक्रम में एक उपयोगकर्ता द्वारा परिभाषित रूपांतरण हो सकता है। addr_impl_refपहले से ही रूपांतरण अनुक्रम के उपयोग को टाइप करने के लिए मजबूर करने के लिए, हम किसी भी रूपांतरण ऑपरेटर को "अक्षम" करते हैं जो कि प्रकार के साथ आता है।

इस प्रकार f(T&,long)अधिभार का चयन किया जाता है (और इंटीग्रल प्रोमोशन का प्रदर्शन)।

किसी अन्य प्रकार के लिए क्या होता है?

इस प्रकार f(T&,long)अधिभार का चयन किया जाता है, क्योंकि वहाँ प्रकार मेल नहीं खाता हैT* पैरामीटर पैरामीटर से ।

नोट: बोरलैंड संगतता के बारे में फाइल में टिप्पणी से, सरणियों संकेत करने के लिए क्षय नहीं है, लेकिन संदर्भ द्वारा पारित कर रहे हैं।

इस अधिभार में क्या होता है?

हम operator&प्रकार पर लागू होने से बचना चाहते हैं , क्योंकि यह अतिभारित हो सकता है।

reinterpret_castइस काम के लिए इस्तेमाल की जा सकने वाली मानक गारंटी (देखें @ माटेयो इटालिया का जवाब: 5.2.10 / 10)।

बूस्टर कंपाइलर चेतावनियों से बचने के लिए (और उन्हें हटाने के लिए ठीक से उपयोग करता है) के साथ कुछ बारीकियों constऔर volatileक्वालिफायर जोड़ता है const_cast

  • कास्ट T&कियाchar const volatile&
  • पट्टी constऔरvolatile
  • &पता लेने के लिए ऑपरेटर को आवेदन करें
  • कास्ट वापस करने के लिए T*

const/ volatileकरतब दिखाने काला जादू का एक सा है, लेकिन यह (बजाय 4 भार के प्रदान करने की तुलना में) काम सरल बना देता है। ध्यान दें कि चूंकि Tअयोग्य है, अगर हम पास करते हैं ghost const&, तो T*हैghost const* इस प्रकार क्वालिफायर वास्तव में खो नहीं किया गया है,।

EDIT: पॉइंटर ओवरलोड का उपयोग पॉइंटर से फ़ंक्शंस के लिए किया जाता है, मैंने उपरोक्त स्पष्टीकरण में कुछ संशोधन किया है। मुझे अभी भी समझ नहीं आया कि यह क्यों जरूरी है।

निम्न आइडोन आउटपुट इसे कुछ हद तक समेटता है


2
"अगर हम एक पॉइंटर पास करते हैं तो क्या होता है?" हिस्सा गलत है। अगर हम किसी सूचक को U टाइप करते हैं, तो एड्रेसऑफ़ फ़ंक्शन टाइप करता है कि 'T' 'U *' का अनुमान लगाता है और addr_impl_ref में दो अधिभार होंगे: 'f (U * &, long)' और 'f (U **), int) ', जाहिर है कि पहले वाले का चयन किया जाएगा।
कोन्स्टेंटिन ओज़नोबीहिन

@Konstantin: सही है, मैंने सोचा था कि दो fअतिभार जहां कार्य खाका बनाते हैं , जबकि वे एक खाका वर्ग के नियमित सदस्य कार्य हैं, इसे इंगित करने के लिए धन्यवाद। (अब मुझे केवल यह पता लगाने की ज़रूरत है कि अधिभार का उपयोग क्या है, किसी भी टिप?)
मैथ्यू एम।

यह एक महान, अच्छी तरह से समझाया गया उत्तर है। मुझे लगता है कि यह "बस के माध्यम से डाली" की तुलना में थोड़ा अधिक था char*। धन्यवाद, मैथ्यू।
जेम्स मैकनेलिस

@ जेम्स: मुझे @ कोंस्टैंटिन से बहुत मदद मिली है, जो किसी भी समय छड़ी से मेरे सिर पर प्रहार करेगा: मैंने गलती की है: डी
मैथ्यू एम।

3
ऐसे प्रकारों के आसपास काम करने की आवश्यकता क्यों होगी जिनके पास रूपांतरण फ़ंक्शन है? क्या यह किसी भी रूपांतरण समारोह को लागू करने के लिए सटीक मैच को पसंद नहीं करेगा T*? संपादित करें: अब मैं देख रहा हूँ। यह होगा, लेकिन इस 0तर्क के साथ यह एक क्राइस-क्रॉस में समाप्त हो जाएगा, इसलिए अस्पष्ट होगा।
जोहान्स स्काउब -

99

का उपयोग करें std::addressof

आप इसे पर्दे के पीछे से करने के बारे में सोच सकते हैं:

  1. ऑब्जेक्ट को संदर्भ-से-चार के रूप में पुन: लिखें
  2. उस का पता ले लो (अधिभार नहीं कहेंगे)
  3. अपने प्रकार के पॉइंटर को पॉइंटर वापस कास्ट करें।

मौजूदा कार्यान्वयन (Boost.Addressof सहित) ठीक वैसा ही करते हैं, जैसे कि अतिरिक्त देखभाल constऔर volatileयोग्यता।


16
मुझे यह स्पष्टीकरण चुने गए से बेहतर लगता है क्योंकि इसे आसानी से समझा जा सकता है।
स्लेज

49

पीछे की चाल boost::addressofऔर @ ल्यूक डैंटन द्वारा प्रदान किया गया कार्यान्वयन जादू के आधार पर निर्भर करता है reinterpret_cast; मानक स्पष्ट रूप से §5.2.10 ¶10 पर बताता है कि

टाइप का एक लवल्यू एक्सप्रेशन T1"रेफरेंस" टाइप किया जा सकता है, T2अगर टाइप "पॉइंटर" का एक्सप्रेशन T1स्पष्ट रूप से टाइप T2करके "पॉइंटर" में परिवर्तित किया जा सकता है reinterpret_cast। यही है, एक संदर्भ कलाकारों reinterpret_cast<T&>(x)का *reinterpret_cast<T*>(&x)बिल्ट-इन &और *ऑपरेटरों के साथ रूपांतरण के समान प्रभाव पड़ता है । परिणाम एक अंतराल है जो स्रोत लैवल्यू के समान वस्तु को संदर्भित करता है, लेकिन एक अलग प्रकार के साथ।

अब, यह हमें एक मनमाना वस्तु संदर्भ को एक char &(cv योग्यता के साथ यदि संदर्भ cv-योग्य है) में परिवर्तित करने की अनुमति देता है , क्योंकि किसी भी सूचक को (संभवतः cv- योग्य) में परिवर्तित किया जा सकता है char *। अब जब हमारे पास एक है char &, ऑब्जेक्ट पर ओवरलोडिंग करने वाला ऑपरेटर अब प्रासंगिक नहीं है, और हम अंतर्निहित &ऑपरेटर के साथ पता प्राप्त कर सकते हैं ।

बूस्ट कार्यान्वयन सीवी-योग्य वस्तुओं के साथ काम करने के लिए कुछ कदम जोड़ता है: पहला करने के reinterpret_castलिए किया जाता है const volatile char &, अन्यथा एक सादे char &कास्ट constऔर / या volatileसंदर्भों के लिए काम नहीं करेगा ( reinterpret_castहटा नहीं सकता const)। तब constऔर के volatileसाथ हटा दिया जाता है const_cast, पते के साथ लिया जाता है &, और reinterpet_cast"सही" प्रकार के लिए एक अंतिम किया जाता है।

const_castदूर करने के लिए की जरूरत है const/ volatileकि गैर स्थिरांक / अस्थिर संदर्भ में जोड़ा जा सकता था, लेकिन ऐसा नहीं "नुकसान" क्या एक था करता है const/ volatileक्योंकि अंतिम, पहली जगह में संदर्भ reinterpret_castइच्छा सीवी-योग्यता फिर से जोड़ना अगर यह था वहाँ पहली जगह में ( reinterpret_castनहीं निकाल सकते const, लेकिन यह जोड़ सकते हैं)।

बाकी कोड के रूप में addressof.hpp, ऐसा लगता है कि इसमें से अधिकांश वर्कअराउंड के लिए है। static inline T * f( T * v, int )केवल बोर्लेन्ड संकलक के लिए आवश्यक प्रतीत हो रहा है, लेकिन इसकी उपस्थिति के लिए की जरूरत का परिचय addr_impl_ref, अन्यथा सूचक प्रकार इस दूसरे अधिभार द्वारा पकड़ा किया जाएगा।

संपादित करें : विभिन्न अधिभार का एक अलग कार्य होता है, @Matthieu M. उत्कृष्ट उत्तर देखें

खैर, मुझे अब इस बात पर यकीन नहीं है; मुझे उस कोड की और जांच करनी चाहिए, लेकिन अब मैं रात का खाना बना रहा हूं :), मैं बाद में इस पर एक नज़र डालूंगा।


मैथ्यू एम। स्पष्टीकरण सूचक को पासऑफ को पास करने के बारे में गलत है। इस तरह के संपादन के साथ अपने महान जवाब को खराब न करें :)
कॉन्सटेंटिन ओज़ोनोबिन

"अच्छा भूख", आगे की जांच से पता चलता है कि कार्यों के संदर्भ में अधिभार को कहा जाता है void func(); boost::addressof(func);। हालाँकि, अधिभार को हटाने से gcc 4.3.4 को कोड संकलित करने और समान आउटपुट का निर्माण करने से नहीं रोकता है, इसलिए मुझे अभी भी समझ में नहीं आया है कि इस अधिभार का होना क्यों आवश्यक है।
Matthieu M.

@ मैथ्यू: यह gcc में एक बग प्रतीत होता है। 13.3.3.2 के अनुसार T & T दोनों कार्य के लिए अविभाज्य हैं। पहला एक पहचान रूपांतरण है और दूसरा एक "सटीक मिलान" रैंक (13.3.3.1.1 तालिका 9) वाले फ़ंक्शन-टू-पॉइंटर रूपांतरण है। इसलिए अतिरिक्त तर्क होना आवश्यक है।
कोन्स्टेंटिन ओज़नोबीहिन

@ मैथ्यू: बस इसे gcc 4.3.4 ( ideone.com/2f34P ) के साथ आज़माया और उम्मीद के मुताबिक अस्पष्टता मिली। क्या आपने एड्रेसऑफ़ कार्यान्वयन या फ़्री फ़ंक्शन टेम्प्लेट जैसे ओवरलोड सदस्य कार्यों की कोशिश की? बाद वाले (जैसे ideone.com/vjCRs ) के परिणामस्वरूप 'T *' ओवरलोड को टेम्लेट तर्क कटौती नियमों (14.8.2.1/2) के कारण चुना जाएगा।
कोंस्टेंटिन ओज़नोबीहिन

2
@ कुरसीगू: आपको क्यों लगता है कि यह करना चाहिए? मैंने विशिष्ट C ++ मानक भागों को संदर्भित किया है, जो यह बताता है कि संकलक को क्या करना चाहिए और सभी संकलक जिनके पास मेरी पहुंच है (सहित, लेकिन केवल 4.3.4 तक सीमित नहीं है, comeau-online, VC6.0-VC2010) जैसा मैंने वर्णन किया है, अस्पष्टता की रिपोर्ट करें। क्या आप इस मामले के बारे में अपने तर्क को विस्तृत कर सकते हैं?
कॉन्सटेंटिन ओज़ोनोबिन 12

11

मैंने ऐसा करने का कार्यान्वयन देखा है addressof:

char* start = &reinterpret_cast<char&>(clyde);
ghost* pointer_to_clyde = reinterpret_cast<ghost*>(start);

मुझसे मत पूछो कि यह कैसे अनुरूप है!


5
कानूनी। char*नियमों को टाइप करने के लिए सूचीबद्ध अपवाद है।
पिल्ला

6
@DeadMG मैं यह नहीं कह रहा हूं कि यह अनुरूप नहीं है। मैं कह रहा हूं कि आपको मुझसे नहीं पूछना चाहिए :)
ल्यूक डैंटन

1
@DeadMG यहां कोई एलियासिंग समस्या नहीं है। सवाल यह है: reinterpret_cast<char*>अच्छी तरह से परिभाषित है।
जिज्ञासु

2
@curiousguy और इसका उत्तर हां में है, इसे हमेशा किसी भी पॉइंटर प्रकार को डालने की अनुमति दी जाती है [unsigned] char *और जिससे पॉइंट-एट ऑब्जेक्ट का ऑब्जेक्ट प्रतिनिधित्व पढ़ा जाता है। यह एक और क्षेत्र है जहां charविशेष विशेषाधिकार हैं।
underscore_d

@underscore_d सिर्फ इसलिए कि एक कलाकार को "हमेशा अनुमति दी जाती है" का मतलब यह नहीं है कि आप कलाकारों के परिणाम के साथ कुछ भी कर सकते हैं।
जिज्ञासु

5

बूस्ट :: एड्रेसोफ़ और इसके कार्यान्वयन पर एक नज़र डालें ।


1
बूस्ट कोड, जबकि दिलचस्प है, यह नहीं बताता है कि इसकी तकनीक कैसे काम करती है (न ही यह बताती है कि दो अधिभार की आवश्यकता क्यों है)।
जेम्स मैकनेलिस

क्या आपका मतलब है 'स्टैटिक इनलाइन T * f (T * v, int)' ओवरलोड? ऐसा लगता है कि यह केवल बोरलैंड सी वर्कअराउंड के लिए आवश्यक है। वहाँ इस्तेमाल दृष्टिकोण बहुत सीधा है। एकमात्र सूक्ष्म (अमानवीय) चीज 'T &' का 'char &' में रूपांतरण है। हालांकि मानक, 'T *' से 'char *' तक कास्ट करने की अनुमति देता है, इसलिए रेफरेंस कास्टिंग के लिए ऐसी कोई आवश्यकता नहीं है। फिर भी, कोई यह उम्मीद कर सकता है कि यह अधिकांश संकलक पर समान रूप से काम करे।
कॉन्स्टेंटिन ओज़नोबीहिन

@Konstantin: ओवरलोड का उपयोग किया जाता है क्योंकि एक पॉइंटर के लिए, addressofपॉइंटर को ही लौटाता है। यह तर्क है कि यह वही है जो उपयोगकर्ता चाहता था या नहीं, लेकिन यह कैसे निर्दिष्ट है।
मैथ्यू एम।

@ मैथ्यू: क्या आपको यकीन है? जहां तक ​​मैं बता सकता हूं, किसी भी प्रकार (पॉइंटर प्रकार सहित) को एक के अंदर लपेटा जाता है addr_impl_ref, इसलिए पॉइंटर ओवरलोड को कभी भी नहीं बुलाया जाना चाहिए ...
मैटेओ इटालिया

1
@KonstantinOznobihin यह वास्तव में सवाल का जवाब नहीं देता है, जैसा कि आप सभी कहते हैं कि उत्तर की तलाश कहां है , उत्तर नहीं है
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.