ओवरलोड को कैसे करें :: स्वैप ()


115

std::swap()कई एसटीडी कंटेनर (जैसे द्वारा किया जाता है std::listऔर std::vector) छँटाई और यहां तक कि काम के दौरान।

लेकिन std कार्यान्वयन swap()बहुत सामान्यीकृत है और कस्टम प्रकारों के लिए अक्षम है।

इस प्रकार std::swap()एक कस्टम प्रकार के विशिष्ट कार्यान्वयन के साथ ओवरलोडिंग करके दक्षता प्राप्त की जा सकती है । लेकिन आप इसे कैसे लागू कर सकते हैं ताकि इसका उपयोग एसटीडी कंटेनरों द्वारा किया जा सके?


संबंधित जानकारी: en.cppreference.com/w/cpp/concept/Swappable
Pharap

Swappable पेज के लिए ले जाया en.cppreference.com/w/cpp/named_req/Swappable
एंड्रयू Keeton

जवाबों:


135

स्वैप ओवरलोड करने का सही तरीका यह है कि आप जो नाम लिख रहे हैं उसे उसी नामस्थान में लिखें, ताकि यह तर्क-निर्भर लुकअप (ADL) के माध्यम से पाया जा सके । एक विशेष रूप से आसान काम है:

class X
{
    // ...
    friend void swap(X& a, X& b)
    {
        using std::swap; // bring in swap for built-in types

        swap(a.base1, b.base1);
        swap(a.base2, b.base2);
        // ...
        swap(a.member1, b.member1);
        swap(a.member2, b.member2);
        // ...
    }
};

11
C ++ 2003 में यह सबसे अच्छा अंडरस्क्राइब है। अधिकांश कार्यान्वयन स्वैप को खोजने के लिए ADL का उपयोग करते हैं, लेकिन नहीं यह अनिवार्य नहीं है, इसलिए आप इस पर भरोसा नहीं कर सकते। आप ओपी द्वारा दिखाए गए अनुसार एक विशिष्ट ठोस प्रकार के लिए std :: swap विशेषज्ञ कर सकते हैं; बस उस प्रकार के व्युत्पन्न वर्गों के लिए उपयोग करने के लिए उस विशेषज्ञता की अपेक्षा न करें।
डेव अब्राहम्स

15
मुझे यह जानकर आश्चर्य होगा कि कार्यान्वयन अभी भी सही स्वैप खोजने के लिए ADL का उपयोग नहीं करते हैं। यह समिति का एक पुराना मुद्दा है। यदि आपका कार्यान्वयन स्वैप खोजने के लिए ADL का उपयोग नहीं करता है, तो बग रिपोर्ट दर्ज करें।
हावर्ड हिनांट

3
@ साचा: सबसे पहले, मैं नाम स्थान पर कार्य को परिभाषित कर रहा हूं क्योंकि यह एकमात्र प्रकार की परिभाषा है जो सामान्य कोड के लिए मायने रखती है। क्योंकि इंट एट। अल। सदस्य कार्य नहीं कर सकते हैं, std :: सॉर्ट एट। अल। एक नि: शुल्क फ़ंक्शन का उपयोग करना है; वे प्रोटोकॉल स्थापित करते हैं। दूसरा, मुझे नहीं पता कि आप दो क्रियान्वयनों पर आपत्ति क्यों करते हैं, लेकिन अधिकांश वर्ग अक्षम होने के कारण बर्बाद हो जाते हैं यदि आप गैर-सदस्य स्वैप नहीं कर सकते। ओवरलोडिंग नियमों से यह सुनिश्चित होता है कि यदि दोनों घोषणाओं को देखा जाता है, तो स्वैप के बिना योग्यता के रूप में अधिक विशिष्ट एक (यह एक) चुना जाएगा।
डेव अब्राहम

5
@ मोज़ाज़ 314: यह निर्भर करता है। A std::sortजो ADL का उपयोग तत्वों को स्वैप करने के लिए करता है वह गैर-अनुरूपण C ++ 03 है लेकिन C ++ 11 के अनुरूप है। इसके अलावा, क्यों -1 इस तथ्य के आधार पर कि ग्राहक गैर-मुहावरेदार कोड का उपयोग कर सकते हैं?
जोजी

4
@ कुरसी: यदि मानक पढ़ना सिर्फ मानक पढ़ने का एक साधारण मामला था, तो आप सही होंगे :-)। दुर्भाग्य से, लेखकों की मंशा मायने रखती है। इसलिए यदि मूल आशय यह था कि एडीएल का उपयोग किया जा सकता है या इसका उपयोग किया जाना चाहिए, तो यह अनिर्दिष्ट है। यदि नहीं, तो यह सिर्फ C ++ 0x के लिए एक साधारण पुराना ब्रेकिंग परिवर्तन है, यही कारण है कि मैंने "सबसे अच्छा" अंडरस्क्राइब किया।
डेव अब्राहम

70

ध्यान रहे मोज़ाज़ 314

यहाँ जेनेरिक std::algorithmकॉलिंग के प्रभावों का एक अनुकरण है std::swap, और उपयोगकर्ता नामस्थान std में अपनी स्वैप प्रदान करते हैं। जैसा कि यह एक प्रयोग है, इस सिमुलेशन के namespace expबजाय का उपयोग करता है namespace std

// simulate <algorithm>

#include <cstdio>

namespace exp
{

    template <class T>
    void
    swap(T& x, T& y)
    {
        printf("generic exp::swap\n");
        T tmp = x;
        x = y;
        y = tmp;
    }

    template <class T>
    void algorithm(T* begin, T* end)
    {
        if (end-begin >= 2)
            exp::swap(begin[0], begin[1]);
    }

}

// simulate user code which includes <algorithm>

struct A
{
};

namespace exp
{
    void swap(A&, A&)
    {
        printf("exp::swap(A, A)\n");
    }

}

// exercise simulation

int main()
{
    A a[2];
    exp::algorithm(a, a+2);
}

मेरे लिए यह प्रिंट आउट:

generic exp::swap

यदि आपका कंपाइलर कुछ अलग प्रिंट करता है तो यह टेम्प्लेट के लिए "दो-चरण लुकअप" को सही ढंग से लागू नहीं कर रहा है।

यदि आपका कंपाइलर अनुरूप है (C ++ 98/03/11 में से किसी में), तो यह वही आउटपुट देगा जो मैं दिखाता हूं। और उस स्थिति में आप जो डरते हैं वही होगा, होता है। और अपने swapनामस्थान std( exp) में डालने से ऐसा होने से नहीं रोका।

डेव और मैं दोनों समिति सदस्य हैं और एक दशक से मानक के इस क्षेत्र में काम कर रहे हैं (और हमेशा एक-दूसरे के साथ समझौते में नहीं)। लेकिन यह मुद्दा लंबे समय से सुलझा हुआ है, और हम दोनों इस बात पर सहमत हैं कि इसे कैसे सुलझाया गया है। इस क्षेत्र में अपने स्वयं के जोखिम पर डेव के विशेषज्ञ की राय / जवाब की अवहेलना करें।

C ++ 98 प्रकाशित होने के बाद यह समस्या सामने आई। 2001 के बारे में डेव और मैंने इस क्षेत्र में काम करना शुरू किया । और यह आधुनिक समाधान है:

// simulate <algorithm>

#include <cstdio>

namespace exp
{

    template <class T>
    void
    swap(T& x, T& y)
    {
        printf("generic exp::swap\n");
        T tmp = x;
        x = y;
        y = tmp;
    }

    template <class T>
    void algorithm(T* begin, T* end)
    {
        if (end-begin >= 2)
            swap(begin[0], begin[1]);
    }

}

// simulate user code which includes <algorithm>

struct A
{
};

void swap(A&, A&)
{
    printf("swap(A, A)\n");
}

// exercise simulation

int main()
{
    A a[2];
    exp::algorithm(a, a+2);
}

आउटपुट है:

swap(A, A)

अपडेट करें

एक अवलोकन किया गया है कि:

namespace exp
{    
    template <>
    void swap(A&, A&)
    {
        printf("exp::swap(A, A)\n");
    }

}

काम करता है! तो क्यों नहीं इस्तेमाल करते हैं?

इस मामले पर विचार करें कि आपका Aवर्ग टेम्पलेट है:

// simulate user code which includes <algorithm>

template <class T>
struct A
{
};

namespace exp
{

    template <class T>
    void swap(A<T>&, A<T>&)
    {
        printf("exp::swap(A, A)\n");
    }

}

// exercise simulation

int main()
{
    A<int> a[2];
    exp::algorithm(a, a+2);
}

अब यह फिर से काम नहीं करता है। :-(

तो आप swapनामस्थान एसटीडी में रख सकते हैं और यह काम कर सकता है। लेकिन आपको इस बात के लिए याद रखना होगा कि जब आपके पास कोई खाका हो तो उस मामले के लिए नाम स्थान swapमें रखें । और चूंकि दोनों ही मामलों अगर तुम डाल काम करेंगे में के नाम स्थान है, यह (और सिखाने दूसरों के लिए) बस कर कि एक तरह से करने के लिए सिर्फ याद करने के लिए आसान है।AA<T>swapA


4
विस्तृत जवाब के लिए बहुत बहुत धन्यवाद। मैं इस बारे में स्पष्ट रूप से कम जानकार हूं और वास्तव में सोच रहा था कि कैसे ओवरलोडिंग और विशेषज्ञता अलग व्यवहार उत्पन्न कर सकती है। हालाँकि, मैं ओवरलोडिंग नहीं बल्कि विशेषज्ञता का सुझाव दे रहा हूँ। जब मैंने template <>आपका पहला उदाहरण रखा तो मुझे exp::swap(A, A)gcc से आउटपुट मिला। तो, विशेषज्ञता क्यों पसंद नहीं है?
वॉल्ट्रेवो

1
वाह! यह वास्तव में ज्ञानवर्धक है। आपने मुझे निश्चित रूप से आश्वस्त किया है। मुझे लगता है कि मैं आपके सुझाव को थोड़ा संशोधित करूँगा और डेव अब्राहम्स के इन-क्लास फ्रेंड सिंटैक्स का उपयोग करूँगा (अरे मैं इसे ऑपरेटर के लिए इस्तेमाल कर सकता हूँ << भी! :-)), जब तक कि आपके पास इससे बचने का कोई कारण न हो (संकलन के अलावा) अलग से)। इसके अलावा, क्या आपको लगता using std::swapहै कि हेडर फ़ाइलों के अंदर "कभी भी स्टेटमेंट्स का उपयोग नहीं करना" एक अपवाद है? वास्तव में, using std::swapअंदर क्यों नहीं डाला गया <algorithm>? मुझे लगता है कि यह लोगों के कोड के एक छोटे से अल्पसंख्यक को तोड़ सकता है। शायद सहायक समर्थन और अंततः इसे डाल दिया?
वॉल्ट्रेवो

3
इन-क्लास मित्र वाक्यविन्यास ठीक होना चाहिए। मैं using std::swapआपके हेडर के भीतर फंक्शन स्कोप को सीमित करने की कोशिश करूंगा । हाँ, swapलगभग एक खोजशब्द है। लेकिन नहीं, यह काफी कीवर्ड नहीं है। तो सबसे अच्छा यह सभी नामस्थानों तक निर्यात न करें जब तक कि आपको वास्तव में नहीं करना है। swapबहुत पसंद है operator==। सबसे बड़ा अंतर यह है कि कभी भी operator==योग्य नाम स्थान वाक्यविन्यास के साथ कॉल करने के बारे में नहीं सोचता (यह सिर्फ बहुत बदसूरत होगा)।
हावर्ड हिनांट

15
@ नीलकरक: आप जिस चीज़ को जटिलता के रूप में देख रहे हैं वह बस बहुत सारे गलत जवाब हैं। डेव अब्राहम के सही उत्तर के बारे में कुछ भी जटिल नहीं है: "स्वैप को अधिभारित करने का सही तरीका यह है कि आप जो नाम लिख रहे हैं उसे उसी नामस्थान में लिखें, ताकि यह तर्क-निर्भर लुकअप (ADL) के माध्यम से पाया जा सके।"
हावर्ड हिनान्ट

2
@ कोटेशोट: क्षमा करें। हर्ब 1998 से इस संदेश को पाने की कोशिश कर रहा है: gotw.ca/publications/mill02.htm वह इस लेख में स्वैप का उल्लेख नहीं करता है। लेकिन यह हर्ब के इंटरफेस सिद्धांत का सिर्फ एक और अनुप्रयोग है।
हावर्ड हिनान्ट

53

Std :: swap को ओवरलोड करने के लिए आपको (C ++ मानक द्वारा) अनुमति नहीं दी जाती है, हालाँकि आपको विशेष रूप से अपने स्वयं के प्रकारों के लिए टेम्पलेट नाम जोड़ने की अनुमति है। उदाहरण के लिए

namespace std
{
    template<>
    void swap(my_type& lhs, my_type& rhs)
    {
       // ... blah
    }
}

तब std कंटेनरों में usages (और कहीं भी) सामान्य के बजाय आपकी विशेषज्ञता को ले जाएगा।

यह भी ध्यान दें कि स्वैप का आधार वर्ग कार्यान्वयन प्रदान करना आपके व्युत्पन्न प्रकारों के लिए पर्याप्त नहीं है। जैसे अगर आपके पास है

class Base
{
    // ... stuff ...
}
class Derived : public Base
{
    // ... stuff ...
}

namespace std
{
    template<>
    void swap(Base& lha, Base& rhs)
    {
       // ...
    }
}

यह बेस कक्षाओं के लिए काम करेगा, लेकिन यदि आप दो व्युत्पन्न वस्तुओं को स्वैप करने की कोशिश करते हैं, तो यह एसटीडी से जेनेरिक संस्करण का उपयोग करेगा क्योंकि टेम्पलेटेड स्वैप एक सटीक मिलान है (और यह आपकी व्युत्पन्न वस्तुओं के 'आधार' भागों को स्वैप करने की समस्या से बचा जाता है) )।

नोट: मैंने अपने अंतिम उत्तर से गलत बिट्स को हटाने के लिए इसे अपडेट किया है। डी 'ओह! (इसे इंगित करने के लिए धन्यवाद puetzk और j_random_hacker)


1
ज्यादातर अच्छी सलाह है, लेकिन मैं -1 के लिए सूक्ष्म अंतर के कारण puetzk द्वारा std नाम स्थान (जो C ++ मानक द्वारा अनुमति दी गई है) और ओवरलोडिंग (जो नहीं है) की अनुमति देता है।
j_random_hacker

11
नीचा दिखाया गया है क्योंकि स्वैप को अनुकूलित करने का सही तरीका अपने स्वयं के नामस्थान में है (जैसा कि डेव अब्राहम एक अन्य उत्तर में बताते हैं)।
हावर्ड हिनांट

2
डाउनवोटिंग के मेरे कारण हावर्ड के
डेव अब्राहम

13
@ हावर्डहिनेंट, डेव अब्राहम: मैं असहमत हूं। किस आधार पर आप दावा करते हैं कि आपका विकल्प "सही" तरीका है? जैसा कि पुटज़क ने मानक से उद्धृत किया है, यह विशेष रूप से अनुमति है। जब मैं इस मुद्दे पर नया हूं तो मुझे वास्तव में वह तरीका पसंद नहीं है जिसकी आप वकालत करते हैं क्योंकि अगर मैं फू को परिभाषित करता हूं और उस तरह से स्वैप करता हूं तो कोई और जो मेरे कोड का उपयोग करता है, वह स्वैप का उपयोग करने की संभावना है :: स्वैप (ए, बी) के बजाय स्वैप () फू पर ए, बी), जो चुपचाप अक्षम डिफ़ॉल्ट संस्करण का उपयोग करता है।
वॉल्ट्रेवो

5
@ Mozza314: टिप्पणी क्षेत्र के स्थान और स्वरूपण बाधाओं ने मुझे आपको पूरी तरह से उत्तर देने की अनुमति नहीं दी। कृपया जवाब देखिए मैंने "अटेंशन मोजा 314" शीर्षक जोड़ा है।
हावर्ड हिनांट

29

हालांकि यह सही है कि किसी को आम तौर पर std :: namespace में सामान नहीं जोड़ना चाहिए, उपयोगकर्ता-परिभाषित प्रकारों के लिए टेम्पलेट विशेषज्ञता को जोड़ना विशेष रूप से अनुमति है। कार्यों को ओवरलोड करना नहीं है। यह एक सूक्ष्म अंतर है :-)

17.4.3.1/1 यह एक सी ++ प्रोग्राम के लिए अपरिभाषित है, जब तक कि अन्यथा निर्दिष्ट न हो तब तक नामस्थान एसटीडी के साथ नाम स्थान या नाम स्थान की घोषणाओं या परिभाषाओं को जोड़ना। एक प्रोग्राम किसी भी मानक लाइब्रेरी टेम्पलेट के लिए नेमस्पेस std में टेम्प्लेट स्पेशलाइज़ेशन जोड़ सकता है। मानक पुस्तकालय की ऐसी विशेषज्ञता (पूर्ण या आंशिक) का परिणाम अपरिभाषित व्यवहार में होता है जब तक कि घोषणा बाहरी लिंकेज के उपयोगकर्ता-परिभाषित नाम पर निर्भर न हो और जब तक कि टेम्पलेट विशेषज्ञता मूल टेम्पलेट के लिए मानक पुस्तकालय आवश्यकताओं को पूरा न करे।

एसटीडी की एक विशेषज्ञता :: स्वैप जैसी दिखती है:

namespace std
{
    template<>
    void swap(myspace::mytype& a, myspace::mytype& b) { ... }
}

टेम्पलेट के बिना <> बिट यह एक अधिभार होगा, जो एक विशेषज्ञता के बजाय अपरिभाषित है, जिसकी अनुमति है। @ विल्का के डिफ़ॉल्ट नामस्थान को बदलने का सुझाव उपयोगकर्ता कोड के साथ काम कर सकता है (कोएनिग लुकअप के कारण नेमस्पेस-कम संस्करण को प्राथमिकता दी), लेकिन इसकी गारंटी नहीं है, और वास्तव में ऐसा नहीं माना जाता है (एसटीएल कार्यान्वयन पूरी तरह से उपयोग करना चाहिए। -क्वालिफाइड std :: swap)।

Comp.lang.c ++ पर एक थ्रेड है। विषय के लंबे डिकसियन के साथ मॉडरेट किया गया है। इसमें से अधिकांश आंशिक विशेषज्ञता के बारे में है, हालांकि (जो कि वर्तमान में ऐसा करने का कोई अच्छा तरीका नहीं है)।


7
इस (या कुछ भी) के लिए फ़ंक्शन टेम्पलेट विशेषज्ञता का उपयोग करने का एक कारण गलत है: यह ओवरलोड के साथ खराब तरीकों से बातचीत करता है, जिनमें से स्वैप के लिए कई हैं। उदाहरण के लिए, यदि आप नियमित std :: swap for std :: वेक्टर <mytype> & के विशेषज्ञ हैं, तो आपका विशेषज्ञता मानक के वेक्टर-विशिष्ट स्वैप के ऊपर नहीं चुना जाएगा, क्योंकि ओवरलोड रिज़ॉल्यूशन के दौरान विशेषज्ञताओं पर विचार नहीं किया जाता है।
डेव अब्राहम

4
यह भी है कि मेयर्स प्रभावी C ++ 3ed (आइटम 25, पीपी 106-112) में सिफारिश करता है।
jww

2
@DaveAbrahams: यदि आप (स्पष्ट टेम्पलेट तर्क के बिना) विशेषज्ञ हैं, तो आंशिक रूप से ऑर्डर करने से यह vectorसंस्करण की विशेषज्ञता हो जाएगी और इसका उपयोग किया जाएगा
डेविस हेरिंग

1
@DavisHerring वास्तव में, नहीं, जब आप करते हैं कि आंशिक आदेश देने में कोई भूमिका नहीं होती है। समस्या यह नहीं है कि आप इसे बिल्कुल नहीं कह सकते; जाहिरा तौर पर स्वैग के
डेव अब्राहम

2
@DaveAbrahams: जब स्पष्ट विशेषज्ञता एक से अधिक से मेल खाती है, तो विशेषज्ञ को कार्य करने के लिए आंशिक ऑर्डर करना है । ::swapअधिभार में आपको शामिल किया और अधिक से अधिक विशेष है std::swapके लिए अधिभार vector, तो यह कॉल और के उत्तरार्द्ध प्रासंगिक है कोई विशेषज्ञता कैप्चर करता है। मुझे यकीन नहीं है कि यह एक व्यावहारिक समस्या कैसे है (लेकिन न तो मैं यह दावा कर रहा हूं कि यह एक अच्छा विचार है!)।
डेविस हेरिंग
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.