सार्वजनिक मित्र स्वैप सदस्य समारोह


169

कॉपी-एंड-स्वैप-मुहावरे के सुंदर उत्तर में कोड का एक टुकड़ा है जिसे मुझे थोड़ी मदद चाहिए:

class dumb_array
{
public:
    // ...
    friend void swap(dumb_array& first, dumb_array& second) // nothrow
    {
        using std::swap; 
        swap(first.mSize, second.mSize); 
        swap(first.mArray, second.mArray);
    }
    // ...
};

और वह एक नोट जोड़ता है

अन्य दावे हैं कि हमें std :: swap हमारे प्रकार के लिए, एक इन-क्लास स्वैप साथ-साथ एक फ्री-फंक्शन स्वैप आदि प्रदान करना चाहिए, लेकिन यह सब अनावश्यक है: स्वैप का कोई भी उचित उपयोग अयोग्य कॉल के माध्यम से होगा , और हमारे कार्य ADL के माध्यम से मिलेंगे। एक फंक्शन करेंगे।

साथ friendमैं "अमित्र" शर्तों पर एक सा हूँ, मैं मानता चाहिए। तो, मेरे मुख्य प्रश्न हैं:

  • एक नि: शुल्क समारोह की तरह लग रहा है , लेकिन इसके वर्ग शरीर के अंदर?
  • यह swapस्थिर क्यों नहीं है ? यह स्पष्ट रूप से किसी भी सदस्य चर का उपयोग नहीं करता है।
  • "स्वैप का कोई उचित उपयोग एडीएल के माध्यम से स्वैप का पता लगाएगा" ? ADL नामस्थानों की खोज करेगा, है ना? लेकिन क्या यह कक्षाओं के अंदर भी दिखता है? या यहाँ है जहाँ में friendआता है?

साइड-सवाल:

  • C ++ 11 के साथ, क्या मुझे अपना swapएस चिह्नित करना चाहिए noexcept?
  • C ++ 11 और इसकी रेंज-के लिए , क्या मुझे कक्षा के अंदर friend iter begin()और friend iter end()उसी तरह जगह चाहिए ? मुझे लगता है कि friendयहाँ जरूरत नहीं है, है ना?

रेंज-बेस्ड के बारे में साइड क्वेश्चन को ध्यान में रखते हुए: सदस्य कार्यों को लिखना और स्टार्ट एक्सेस () और एंड () पर std नेमस्पेस (.624.6.5) में रेंज एक्सेस को छोड़ना बेहतर होगा, वैश्विक रूप से आंतरिक रूप से इनका उपयोग करने के लिए रेंज-आधारित std नेमस्पेस (देखें .56.5.4)। हालाँकि, यह नकारात्मक पक्ष के साथ आता है कि ये कार्य <iterator> शीर्ष लेख का हिस्सा हैं, यदि आप इसे शामिल नहीं करते हैं, तो आप स्वयं लिखना चाह सकते हैं।
विटस

2
यह स्थिर क्यों नहीं है - क्योंकि कोई friendफ़ंक्शन सदस्य का फ़ंक्शन नहीं है।
aschepler

जवाबों:


175

लिखने के कई तरीके हैं swap, कुछ दूसरों की तुलना में बेहतर हैं। समय के साथ, हालांकि, यह पाया गया कि एकल परिभाषा सबसे अच्छा काम करती है। आइए विचार करें कि एक swapफ़ंक्शन लिखने के बारे में हम कैसे सोच सकते हैं ।


हम पहले देखते हैं कि कंटेनरों में std::vector<>एकल-तर्क सदस्य फ़ंक्शन होता है swap, जैसे:

struct vector
{
    void swap(vector&) { /* swap members */ }
};

स्वाभाविक रूप से, तब, हमारी कक्षा भी सही होनी चाहिए? असल में ऐसा नहीं है। मानक पुस्तकालय में सभी प्रकार की अनावश्यक चीजें हैं , और एक सदस्य swapउनमें से एक है। क्यों? चलो आगे बढ़ें।


हमें क्या करना चाहिए, यह पहचानना है कि विहित क्या है, और इसके साथ काम करने के लिए हमारी कक्षा को क्या करने की आवश्यकता है। और स्वैपिंग की विहित विधि के साथ है std::swap। यही कारण है कि सदस्य फ़ंक्शन उपयोगी नहीं हैं: वे नहीं हैं कि हमें सामान्य रूप से चीजों को कैसे स्वैप करना चाहिए, और उनके व्यवहार पर कोई असर नहीं पड़ता है std::swap

ठीक है, तो std::swapकाम करने के लिए हमें (और प्रदान std::vector<>करना चाहिए था) का एक विशेषज्ञता std::swap, सही?

namespace std
{
    template <> // important! specialization in std is OK, overloading is UB
    void swap(myclass&, myclass&)
    {
        // swap
    }
}

ठीक है कि निश्चित रूप से इस मामले में काम करेंगे, लेकिन इसकी एक विकराल समस्या है: कार्य विशेषज्ञता आंशिक नहीं हो सकती। यही है, हम इसके साथ खाका कक्षाओं को विशेषीकृत नहीं कर सकते हैं, केवल विशेष इंस्टेंटिएशन:

namespace std
{
    template <typename T>
    void swap<T>(myclass<T>&, myclass<T>&) // error! no partial specialization
    {
        // swap
    }
}

यह विधि कुछ समय के लिए काम करती है, लेकिन हर समय नहीं। इसके लिए अवश्य ही एक बेहतर तरीका होना चाहिए। '


वहाँ है! हम एक friendफ़ंक्शन का उपयोग कर सकते हैं , और इसे ADL के माध्यम से पा सकते हैं :

namespace xyz
{
    struct myclass
    {
        friend void swap(myclass&, myclass&);
    };
}

जब हम कुछ स्वैप करना चाहते हैं, तो हम want को जोड़ते हैं std::swap और फिर एक अयोग्य कॉल करते हैं:

using std::swap; // allow use of std::swap...
swap(x, y); // ...but select overloads, first

// that is, if swap(x, y) finds a better match, via ADL, it
// will use that instead; otherwise it falls back to std::swap

एक friendकार्य क्या है ? इस क्षेत्र को लेकर चारों ओर भ्रम की स्थिति है।

इससे पहले कि सी ++ मानकीकरण किया गया था, friendकार्यों कुछ 'मित्र नाम इंजेक्शन "कहा जाता है, जहां कोड से व्यवहार किया था के रूप में अगर अगर समारोह आसपास के नाम स्थान में लिखा गया था। उदाहरण के लिए, ये समान मानक थे:

struct foo
{
    friend void bar()
    {
        // baz
    }
};

// turned into, pre-standard:    

struct foo
{
    friend void bar();
};

void bar()
{
    // baz
}

हालाँकि, जब ADL का आविष्कार किया गया था तो इसे हटा दिया गया था। friendसमारोह तो हो सकता है केवल ADL के माध्यम से पाया जा; यदि आप इसे एक नि: शुल्क फ़ंक्शन के रूप में चाहते हैं, तो इसे घोषित करने की आवश्यकता है ( उदाहरण के लिए इसे देखें )। लेकिन लो! वहाँ एक समस्या थी।

यदि आप सिर्फ उपयोग करते हैं std::swap(x, y), तो आपका अधिभार कभी नहीं मिलेगा , क्योंकि आपने स्पष्ट रूप से कहा है "देखो std, और कहीं नहीं"! यही कारण है कि कुछ लोगों ने दो कार्यों को लिखने का सुझाव दिया: एक एडीएल के माध्यम से पाया जाने वाला एक फ़ंक्शन के रूप में , और दूसरा स्पष्ट std::योग्यता को संभालने के लिए ।

लेकिन जैसा हमने देखा, यह सभी मामलों में काम नहीं कर सकता है, और हम एक बदसूरत गड़बड़ के साथ समाप्त होते हैं। इसके बजाय, मुहावरेदार अदला-बदली दूसरे मार्ग पर चली गई: यह प्रदान करने के लिए कक्षाओं की नौकरी करने के बजाय std::swap, यह सुनिश्चित करने के लिए कि यह swapउपर्युक्त की तरह योग्य उपयोग नहीं करते हैं , यह स्वैपर्स का काम है । और यह बहुत अच्छा काम करता है, जब तक लोग इसके बारे में जानते हैं। लेकिन इसमें समस्या है: यह अयोग्य है कि अयोग्य कॉल का उपयोग करने की आवश्यकता है!

इसे आसान बनाने के लिए, बूस्ट जैसे कुछ पुस्तकालयों ने फ़ंक्शन प्रदान किया boost::swap, जो कि एक संबद्ध नाम के swapसाथ सिर्फ एक अयोग्य कॉल करता है std::swap। यह चीजों को फिर से सफल बनाने में मदद करता है, लेकिन यह अभी भी एक बुमेर है।

ध्यान दें कि C ++ 11 के व्यवहार में कोई परिवर्तन नहीं है std::swap, जो कि मैंने और दूसरों ने गलती से सोचा था कि मामला होगा। यदि आप इससे थोड़ा परेशान थे, तो यहां पढ़ें


संक्षेप में: सदस्य फ़ंक्शन सिर्फ शोर है, विशेषज्ञता बदसूरत और अपूर्ण है, लेकिन friendफ़ंक्शन पूर्ण है और काम करता है। और जब आप स्वैप करते हैं, तो संबंधित के साथ boost::swapया तो उपयोग करें या एक अयोग्य ।swapstd::swap


† अनौपचारिक रूप से, एक नाम जुड़ा हुआ है अगर यह एक फ़ंक्शन कॉल के दौरान माना जाएगा। विवरण के लिए, §3.4.2 पढ़ें। इस मामले में, std::swapआम तौर पर विचार नहीं किया जाता है; लेकिन हम इसे जोड़ सकते हैं (इसे अयोग्य द्वारा माना गया ओवरलोड के सेट में जोड़ सकते हैं swap), जिससे इसे पाया जा सके।


10
मैं असहमत हूं कि सदस्य समारोह सिर्फ शोर है। एक सदस्य फ़ंक्शन उदाहरण के लिए अनुमति देता है std::vector<std::string>().swap(someVecWithData);, जो एक swapमुफ्त फ़ंक्शन के साथ संभव नहीं है क्योंकि दोनों तर्क गैर-कॉन्स्टेंस संदर्भ द्वारा पारित किए जाते हैं।
०१:३४

3
@ गिल्डरन: आप इसे दो लाइनों पर कर सकते हैं। सदस्य फ़ंक्शन होने से DRY सिद्धांत का उल्लंघन होता है।
GMANNICKG

4
@ मन: DRY सिद्धांत लागू नहीं होता है अगर एक दूसरे के कार्यकाल में लागू किया जाता है। अन्यथा कोई भी के कार्यान्वयन के साथ एक वर्ग की वकालत करेंगे operator=, operator+और operator+=है, लेकिन स्पष्ट रूप से प्रासंगिक वर्गों पर उन ऑपरेटरों स्वीकार कर रहे हैं / समरूपता के लिए मौजूद रहने की उम्मीद। मेरे विचार से सदस्य swap+ नेमस्पेस-स्कूप्ड के लिए समान जाता है swap
इलडार्जन

3
@ मुझे लगता है कि यह बहुत अधिक कार्यों पर विचार कर रहा है। थोड़ा ज्ञात है, लेकिन यहां तक ​​कि function<void(A*)> f; if(!f) { }सिर्फ इसलिए असफल हो सकता है क्योंकि यह Aघोषित करता है operator!कि fसमान रूप से अच्छी तरह से fखुद को स्वीकार करता है operator!(संभावना नहीं है, लेकिन हो सकता है)। यदि function<>लेखक ने सोचा कि "ओह, मेरे पास एक 'ऑपरेटर बूल' है, तो मुझे 'ऑपरेटर' क्यों लागू करना चाहिए? यह DRY को कम करेगा!", यह घातक होगा। आपको बस एक के operator!लिए एक कार्यान्वित होना चाहिए A, और Aएक के लिए एक निर्माता होना चाहिए function<...>, और चीजें टूट जाएंगी, क्योंकि दोनों उम्मीदवारों को उपयोगकर्ता परिभाषित रूपांतरणों की आवश्यकता होगी।
जोहान्स शाउब -

1
आइए विचार करें कि एक [सदस्य] स्वैप फ़ंक्शन लिखने के बारे में हम कैसे सोच सकते हैं। स्वाभाविक रूप से, तब, हमारी कक्षा भी सही होनी चाहिए? असल में ऐसा नहीं है। मानक पुस्तकालय में सभी प्रकार की अनावश्यक चीजें हैं , और एक सदस्य स्वैप उनमें से एक है। लिंक किए गए GotW मेंबर स्वैप फंक्शन की वकालत करते हैं।
Xeverous

7

यह कोड समतुल्य है ( लगभग हर तरह से):

class dumb_array
{
public:
    // ...
    friend void swap(dumb_array& first, dumb_array& second);
    // ...
};

inline void swap(dumb_array& first, dumb_array& second) // nothrow
{
    using std::swap; 
    swap(first.mSize, second.mSize); 
    swap(first.mArray, second.mArray);
}

कक्षा के अंदर परिभाषित एक मित्र कार्य है:

  • एनक्लोजिंग नेमस्पेस में रखा गया है
  • खुद ब खुद inline
  • आगे की योग्यता के बिना वर्ग के स्थिर सदस्यों को संदर्भित करने में सक्षम

सटीक नियम अनुभाग में हैं [class.friend](मैं C ++ 0x ड्राफ्ट के अनुच्छेद 6 और 7 को उद्धृत करता हूं):

एक फ़ंक्शन को एक कक्षा के एक मित्र घोषणा में परिभाषित किया जा सकता है यदि और केवल यदि वर्ग एक गैर-स्थानीय वर्ग (9.8) है, तो फ़ंक्शन नाम अयोग्य है, और फ़ंक्शन का नाम स्थान स्कोप है।

इस तरह के एक समारोह में निहित है। एक कक्षा में परिभाषित एक फ्रेंड फंक्शन उस वर्ग के (लेक्सिकल) दायरे में होता है जिसमें इसे परिभाषित किया गया है। कक्षा के बाहर परिभाषित एक मित्र कार्य नहीं है।


2
दरअसल, फ्रेंड फ़ंक्शंस को मानक C ++ में एन्क्लोज़िंग नेमस्पेस में नहीं रखा गया है। पुराने व्यवहार को "मित्र नाम इंजेक्शन" कहा जाता था, लेकिन पहले मानक में प्रतिस्थापित ADL द्वारा अलग कर दिया गया था। के शीर्ष देखें इस । (व्यवहार काफी समान है, हालांकि।)
GManNickG

1
वास्तव में समकक्ष नहीं है। प्रश्न में कोड ऐसा बनाता है जो swapकेवल ADL को दिखाई देता है। यह एनक्लोजिंग नेमस्पेस का सदस्य है, लेकिन इसका नाम अन्य नाम लुकअप फॉर्म में दिखाई नहीं देता है। संपादित करें: मुझे लगता है कि @GMan तेजी से फिर से था :) @Ben यह हमेशा किया गया है कि आईएसओ सी ++ :) में जिस तरह से
Johannes Schaub - litb

2
@ बान: नहीं, दोस्त इंजेक्शन कभी भी एक मानक में मौजूद नहीं था, लेकिन इसका व्यापक रूप से इस्तेमाल किया गया था, इससे पहले कि विचार (और संकलक समर्थन) पर ले जाने के लिए क्यों, लेकिन यह तकनीकी रूप से नहीं है। friendफ़ंक्शंस केवल ADL द्वारा पाए जाते हैं, और यदि उन्हें friendएक्सेस के साथ केवल फ़ंक्शंस होने की आवश्यकता होती है, तो उन्हें friendक्लास के भीतर और सामान्य फ्री फंक्शन घोषणा के रूप में घोषित किया जाना चाहिए । आप इस उत्तर में उस आवश्यकता को देख सकते हैं , उदाहरण के लिए।
GManNickG

2
@towi: क्योंकि फ्रेंड फंक्शन नेमस्पेस स्कोप पर है, इसलिए आपके तीनों सवालों के जवाब स्पष्ट हो जाने चाहिए: (1) यह एक फ्री फंक्शन है, साथ ही इसमें क्लास के प्राइवेट और प्रोटेक्टेड सदस्यों की फ्रेंड एक्सेस है। (२) यह बिल्कुल भी सदस्य नहीं है, न तो उदाहरण और न ही स्थिर। (3) ADL कक्षाओं में खोज नहीं करता है, लेकिन यह ठीक है क्योंकि मित्र फ़ंक्शन में नेमस्पेस स्कोप है।
बेन वोइग्ट

1
@Ben। कल्पना में, फ़ंक्शन एक नाम स्थान सदस्य है, और वाक्यांश "फ़ंक्शन में नेमस्पेस स्कोप" है, यह कहने के लिए व्याख्या की जा सकती है कि फ़ंक्शन एक नामस्थान सदस्य है (यह इस तरह के बयान के संदर्भ पर बहुत निर्भर करता है)। और यह उस नामस्थान में एक नाम जोड़ता है जो केवल ADL को दिखाई देता है (वास्तव में, IIRC इस भाग के अन्य भागों में इस बात का विरोधाभास है कि कोई नाम जोड़ा गया है या नहीं। लेकिन असंगत घोषित करने का पता लगाने के लिए एक नाम के अतिरिक्त की आवश्यकता है। नाम स्थान, इसलिए वास्तव में, एक अदृश्य नाम जोड़ा जाता है। 3.3.1p4 पर नोट देखें)।
जोहान्स स्काउब - १ ९'११ को
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.