C ++ 11 में गैर-सदस्य प्रारंभ और समाप्ति कार्यों का उपयोग क्यों करें?


197

प्रत्येक मानक कंटेनर में उस कंटेनर के लिए पुनरावृत्तियों को वापस करने के लिए एक विधि beginऔर endविधि होती है। हालाँकि, C ++ 11 ने जाहिरा तौर पर नि: शुल्क फ़ंक्शंस पेश किए हैं std::beginऔर std::endजो कॉल करते हैं beginऔर endसदस्य कार्य करते हैं। इसलिए, लिखने के बजाय

auto i = v.begin();
auto e = v.end();

आप लिखेंगे

auto i = std::begin(v);
auto e = std::end(v);

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

तो, सवाल यह है कि वास्तव में मुफ्त फ़ंक्शन संस्करण क्या हैं std::beginऔर std::endउनके संबंधित सदस्य फ़ंक्शन संस्करणों को कॉल करने से परे हैं, और आप उनका उपयोग क्यों करना चाहते हैं?


29
यह एक कम चरित्र है, अपने बच्चों के लिए उन डॉट्स को सहेजें: xkcd.com/297
HostileFork का कहना है कि SE

मैं किसी भी तरह उन्हें इस्तेमाल करने से नफरत करता हूं क्योंकि मुझे std::हर समय दोहराना है ।
माइकल चौरडकिस

जवाबों:


162

आप कैसे कहते हैं .begin()और .end()एक सी सरणी पर?

फ्री-फ़ंक्शंस अधिक सामान्य प्रोग्रामिंग के लिए अनुमति देते हैं क्योंकि उन्हें बाद में जोड़ा जा सकता है, डेटा-संरचना पर आप बदल नहीं सकते हैं।


7
@JonathanMDavis: आप टेम्पलेट प्रोग्रामिंग ट्रिक्स का उपयोग करके endसांख्यिकीय रूप से घोषित सरणियों ( int foo[5]) के लिए हो सकते हैं । एक बार जब यह एक सूचक को क्षय हो जाता है, तो आप निश्चित रूप से भाग्य से बाहर हैं।
Matthieu M.

33
template<typename T, size_t N> T* end(T (&a)[N]) { return a + N; }
ह्यूग

6
@JonathanMDavis: जैसा कि दूसरों ने संकेत दिया है, यह निश्चित रूप से प्राप्त करना संभव है beginऔर endएक सी सरणी पर जब तक आप पहले से ही इसे खुद एक पॉइंटर करने के लिए क्षय नहीं करते हैं - @Hww इसे मंत्र देता है। जैसा कि आप क्यों चाहते हैं: कल्पना कीजिए कि आपने एक वेक्टर का उपयोग करने के लिए एक सरणी (या जो भी कारण से,) का उपयोग करने के लिए एक कोड का उपयोग किया था, को फिर से सक्रिय कर दिया। यदि आप उपयोग कर रहे हैं beginऔर end, और शायद कुछ चतुर टाइपिंग, कार्यान्वयन कोड को बिल्कुल नहीं बदलना पड़ेगा (शायद कुछ टाइपराइफ को छोड़कर)।
कार्ल क्नेटेल

31
@JonathanMDavis: Arrays पॉइंटर्स नहीं हैं। और हर किसी के लिए: इस कभी-प्रमुख भ्रम को समाप्त करने के लिए, (कुछ) संकेत को "क्षय सरण" के रूप में संदर्भित करना बंद करें। भाषा में ऐसी कोई शब्दावली नहीं है, और वास्तव में इसके लिए कोई उपयोग नहीं है। संकेत बिंदु हैं, सरणियाँ सरणियाँ हैं। Arrays को उनके पहले तत्व को स्पष्ट रूप से एक पॉइंटर में बदला जा सकता है, लेकिन अभी भी एक नियमित पुराना पॉइंटर है, जिसमें दूसरों के साथ कोई अंतर नहीं है। बेशक आप एक पॉइंटर के "अंत" को प्राप्त नहीं कर सकते हैं, केस बंद।
GMANNICKG

5
वैसे, सरणियों के अलावा बड़ी संख्या में एपीआई हैं जो कंटेनर को पहलुओं की तरह उजागर करते हैं। जाहिर है कि आप तृतीय पक्ष एपीआई को संशोधित नहीं कर सकते हैं, लेकिन आप आसानी से इन मुक्त खड़े शुरुआत / अंत कार्यों को लिख सकते हैं।
edA-qa mort-ora-y

35

उस मामले पर विचार करें जब आपके पास पुस्तकालय हो जिसमें कक्षा हो:

class SpecialArray;

इसकी 2 विधियाँ हैं:

int SpecialArray::arraySize();
int SpecialArray::valueAt(int);

यह उन मूल्यों पर पुनरावृति करने के लिए जिन्हें आपको इस वर्ग से विरासत में प्राप्त करने begin()और end()मामलों के लिए तरीकों को परिभाषित करने की आवश्यकता है

auto i = v.begin();
auto e = v.end();

लेकिन अगर आप हमेशा उपयोग करते हैं

auto i = begin(v);
auto e = end(v);

तुम यह केर सकते हो:

template <>
SpecialArrayIterator begin(SpecialArray & arr)
{
  return SpecialArrayIterator(&arr, 0);
}

template <>
SpecialArrayIterator end(SpecialArray & arr)
{
  return SpecialArrayIterator(&arr, arr.arraySize());
}

जहां SpecialArrayIteratorकी तरह कुछ है:

class SpecialArrayIterator
{
   SpecialArrayIterator(SpecialArray * p, int i)
    :index(i), parray(p)
   {
   }
   SpecialArrayIterator operator ++();
   SpecialArrayIterator operator --();
   SpecialArrayIterator operator ++(int);
   SpecialArrayIterator operator --(int);
   int operator *()
   {
     return parray->valueAt(index);
   }
   bool operator ==(SpecialArray &);
   // etc
private:
   SpecialArray *parray;
   int index;
   // etc
};

अब iऔर विशेष eरूप से विशेष के मूल्यों के पुनरावृत्ति और उपयोग के लिए कानूनी रूप से उपयोग किया जा सकता है


8
इसमें template<>लाइनें शामिल नहीं होनी चाहिए । आप एक नया फ़ंक्शन ओवरलोड घोषित कर रहे हैं, कोई टेम्प्लेट विशेषज्ञता नहीं।
डेविड स्टोन

33

फ्री beginऔर endफ्री फ़ंक्शंस का उपयोग करने से अप्रत्यक्ष की एक परत जुड़ जाती है। आमतौर पर यह अधिक लचीलापन देने के लिए किया जाता है।

इस मामले में मैं कुछ उपयोगों के बारे में सोच सकता हूं।

सबसे स्पष्ट उपयोग सी-सरणियों (सी पॉइंटर्स नहीं) के लिए है।

एक और है जब एक गैर-अनुरूप कंटेनर पर एक मानक एल्गोरिथ्म का उपयोग करने की कोशिश कर रहा है (यानी कंटेनर एक .begin()विधि याद आ रही है )। मान लें कि आप कंटेनर को ठीक नहीं कर सकते, तो अगला सबसे अच्छा विकल्प beginफ़ंक्शन को अधिभारित करना है। हर्ब सुझाव दे रहा है कि आप हमेशा beginअपने कोड में एकरूपता और स्थिरता को बढ़ावा देने के लिए फ़ंक्शन का उपयोग करें । इसके बजाय यह याद रखने के लिए कि कौन से कंटेनर विधि का समर्थन करते हैं beginऔर जिन्हें फ़ंक्शन की आवश्यकता होती है begin

एक तरफ के रूप में, अगले C ++ रिव्यू में D के छद्म सदस्य संकेतन की नकल करनी चाहिए । अगर a.foo(b,c,d)इसे परिभाषित नहीं किया जाता है तो इसके बजाय कोशिश करता है foo(a,b,c,d)। यह गरीब मनुष्यों की मदद करने के लिए बस एक छोटी सी चीनी है जो विषय को पसंद करते हैं।


5
छद्म सदस्य अंकन सी # की तरह दिखता है /। शुद्ध विस्तार तरीकों । वे विभिन्न स्थितियों के लिए उपयोगी होते हैं, हालांकि - सभी विशेषताओं की तरह - 'दुरुपयोग' के लिए प्रवण हो सकते हैं।
गैरेथ विल्सन

5
छद्म सदस्य संकेतन Intellisense के साथ कोडिंग के लिए एक वरदान है; मारना "ए।" प्रासंगिक क्रियाओं को दिखाता है, सूचियों को याद रखने से मस्तिष्क की शक्ति को मुक्त करता है, और प्रासंगिक एपीआई कार्यों को खोजने में मदद करने से कक्षाओं में गैर-सदस्यीय कार्यों को करने के बिना कार्यक्षमता को रोकने में मदद मिल सकती है।
मैट कर्टिस

सी ++ में इसे प्राप्त करने के प्रस्ताव हैं, जो यूनिफाइड फंक्शन कॉल सिंटैक्स (यूएफसीएस) शब्द का उपयोग करते हैं।
अंडरस्कोर_ड

17

आपके प्रश्न का उत्तर देने के लिए, नि: शुल्क फ़ंक्शन शुरू () और अंत () डिफ़ॉल्ट रूप से कंटेनर के सदस्य .be () और (.end) फ़ंक्शन को कॉल करने से ज्यादा कुछ नहीं करते हैं। से <iterator>, स्वचालित रूप से शामिल है जब आप जैसे मानक कंटेनर से किसी का उपयोग <vector>, <list>आदि, आपको मिलता है:

template< class C > 
auto begin( C& c ) -> decltype(c.begin());
template< class C > 
auto begin( const C& c ) -> decltype(c.begin()); 

आप सवाल का दूसरा हिस्सा यह है कि मुक्त कार्यों को प्राथमिकता दें यदि वे सब करते हैं तो वैसे भी सदस्य कार्यों को कॉल करें। यह वास्तव में इस बात पर निर्भर करता है कि vआपके उदाहरण कोड में किस प्रकार की वस्तु है। यदि v का प्रकार एक मानक कंटेनर प्रकार है, vector<T> v;तो जैसे कि अगर आप मुफ्त या सदस्य कार्यों का उपयोग करते हैं , तो इससे कोई फर्क नहीं पड़ता, वे एक ही काम करते हैं। यदि आपकी वस्तु vअधिक सामान्य है, जैसे निम्नलिखित कोड:

template <class T>
void foo(T& v) {
  auto i = v.begin();     
  auto e = v.end(); 
  for(; i != e; i++) { /* .. do something with i .. */ } 
}

फिर सदस्य फ़ंक्शंस का उपयोग करके आपका कोड T = C सरणियों, C स्ट्रिंग्स, एनम आदि के लिए तोड़ दिया जाता है। गैर-सदस्य फ़ंक्शंस का उपयोग करके, आप एक अधिक सामान्य इंटरफ़ेस का विज्ञापन करते हैं जिसे लोग आसानी से बढ़ा सकते हैं। मुफ्त फ़ंक्शन इंटरफ़ेस का उपयोग करके:

template <class T>
void foo(T& v) {
  auto i = begin(v);     
  auto e = end(v); 
  for(; i != e; i++) { /* .. do something with i .. */ } 
}

कोड अब T = C सरणियों और C स्ट्रिंग्स के साथ काम करता है। अब एडॉप्टर कोड की एक छोटी राशि लिखना:

enum class color { RED, GREEN, BLUE };
static color colors[]  = { color::RED, color::GREEN, color::BLUE };
color* begin(const color& c) { return begin(colors); }
color* end(const color& c)   { return end(colors); }

हम आपके कोड को iterable enums के साथ संगत होने के लिए भी प्राप्त कर सकते हैं। मुझे लगता है कि हर्ब का मुख्य बिंदु यह है कि फ्री फ़ंक्शंस का उपयोग करना सदस्य के फ़ंक्शंस का उपयोग करने के समान आसान है, और यह आपके सी कोड प्रकारों के साथ बैकवर्ड संगतता और नॉन-स्टाल अनुक्रम प्रकारों (और भविष्य-एसएलएल प्रकार!) के साथ आगे संगतता देता है। अन्य डेवलपर्स के लिए कम लागत के साथ।


अच्छे उदाहरण हैं। मैं enumसंदर्भ द्वारा या किसी अन्य मौलिक प्रकार को नहीं लूंगा , हालाँकि; वे अप्रत्यक्ष करने की तुलना में कॉपी करने के लिए सस्ता होगा।
अंडरस्कोर_ड

6

का एक लाभ std::beginऔर std::endहै कि वे बाहरी कक्षाओं के लिए मानक इंटरफेस को लागू करने के लिए विस्तार अंक के रूप में सेवा है।

यदि आप CustomContainerलूप या टेम्प्लेट फ़ंक्शन के लिए श्रेणी-आधारित के साथ वर्ग का उपयोग करना चाहते हैं जो अपेक्षा करता है .begin()और .end()विधियां करता है, तो आपको स्पष्ट रूप से उन विधियों को लागू करना होगा।

यदि वर्ग उन तरीकों को प्रदान करता है, तो यह समस्या नहीं है। जब ऐसा नहीं होता है, तो आपको इसे संशोधित करना होगा *।

यह हमेशा संभव नहीं होता है, उदाहरण के लिए, बाहरी पुस्तकालय, निबंधात्मक वाणिज्यिक और बंद स्रोत एक का उपयोग करते समय।

ऐसी स्थितियों में, std::beginऔर std::endकाम में आते हैं, क्योंकि कोई भी वर्ग को संशोधित किए बिना पुनरावृत्त एपीआई प्रदान कर सकता है, बल्कि मुफ्त कार्यों को ओवरलोड कर सकता है।

उदाहरण: मान लें कि आप ऐसे count_ifफ़ंक्शन को कार्यान्वित करना चाहते हैं जो पुनरावृत्तियों की एक जोड़ी के बजाय एक कंटेनर लेता है। ऐसा कोड इस तरह दिख सकता है:

template<typename ContainerType, typename PredicateType>
std::size_t count_if(const ContainerType& container, PredicateType&& predicate)
{
    using std::begin;
    using std::end;

    return std::count_if(begin(container), end(container),
                         std::forward<PredicateType&&>(predicate));
}

अब, किसी भी वर्ग के लिए जिसे आप इस रिवाज के साथ उपयोग करना चाहते हैं count_if, आपको उन कक्षाओं को संशोधित करने के बजाय केवल दो मुफ्त फ़ंक्शंस जोड़ने होंगे।

अब, C ++ में Argument Dependent Lookup (ADL) नामक एक मैकेनिज्म है , जो इस तरह के दृष्टिकोण को और अधिक लचीला बनाता है।

संक्षेप में, एडीएल का अर्थ है, जब एक संकलक एक अयोग्य फ़ंक्शन (जैसे नाम स्थान के बिना फ़ंक्शन, जैसे के beginबजाय std::begin) का निराकरण करता है , तो यह अपने तर्कों के नामों में घोषित कार्यों पर भी विचार करेगा। उदाहरण के लिए:

namesapce some_lib
{
    // let's assume that CustomContainer stores elements sequentially,
    // and has data() and size() methods, but not begin() and end() methods:

    class CustomContainer
    {
        ...
    };
}

namespace some_lib
{    
    const Element* begin(const CustomContainer& c)
    {
        return c.data();
    }

    const Element* end(const CustomContainer& c)
    {
        return c.data() + c.size();
    }
}

// somewhere else:
CustomContainer c;
std::size_t n = count_if(c, somePredicate);

इस मामले में, यह कोई फर्क नहीं पड़ता कि योग्य नाम हैं some_lib::beginऔर some_lib::end - चूंकि CustomContainerबहुत में some_lib::है, कंपाइलर उन ओवरलोड का उपयोग करेगा count_if

यही कारण है using std::begin;और होने using std::end;में भी है count_if। यह हमें अयोग्य का उपयोग करने की अनुमति देता है beginऔर endइसलिए एडीएल के लिए अनुमति देता है और संकलक को चुनने की अनुमति देता है std::beginऔर std::endजब कोई अन्य विकल्प नहीं मिलते हैं।

हम कुकी खा सकते हैं और कुकी हो सकती है - यानी कस्टम कार्यान्वयन प्रदान करने का एक तरीका है begin/ endजबकि कंपाइलर मानक वाले पर गिर सकता है।

कुछ नोट:

  • उसी कारण से, अन्य समान कार्य हैं: std::rbegin/ rend, std::sizeऔर std::data

  • अन्य उत्तरों का उल्लेख करते हुए, std::संस्करणों में नग्न सरणियों के लिए अतिभार होते हैं। यह उपयोगी है, लेकिन मैंने जो ऊपर वर्णित किया है उसका केवल एक विशेष मामला है।

  • std::beginटेम्प्लेट कोड लिखते समय दोस्तों का उपयोग करना विशेष रूप से अच्छा विचार है, क्योंकि यह उन टेम्प्लेट को अधिक सामान्य बनाता है। गैर-टेम्प्लेट के लिए, जब आप लागू हो, तो बस तरीकों का उपयोग करें।

PS मुझे पता है कि यह पोस्ट लगभग 7 साल पुरानी है। मैं इसके पार आया था क्योंकि मैं एक प्रश्न का उत्तर देना चाहता था जिसे एक डुप्लिकेट के रूप में चिह्नित किया गया था और पता चला कि यहां कोई भी उत्तर ADL का उल्लेख नहीं करता है।


अच्छा जवाब, विशेष रूप से ADL को अधिकता से समझाते हुए, बजाय इसे कल्पना तक छोड़ने के कि जैसे बाकी सभी ने किया था - तब भी जब वे इसे कार्रवाई में दिखा रहे थे!
अंडरस्कोर_ड

5

जबकि गैर-सदस्य कार्य मानक कंटेनरों के लिए कोई लाभ प्रदान नहीं करते हैं, उनका उपयोग करने से अधिक सुसंगत और लचीली शैली लागू होती है। यदि आप कुछ समय के लिए मौजूदा गैर-एसटीडी कंटेनर क्लास का विस्तार करना चाहते हैं, तो आप मौजूदा क्लास की परिभाषा को बदलने के बजाय, मुक्त कार्यों के अधिभार को परिभाषित करेंगे। अतः गैर-एसटीडी कंटेनरों के लिए वे बहुत उपयोगी होते हैं और हमेशा मुफ़्त फ़ंक्शंस का उपयोग करने से आपका कोड अधिक लचीला हो जाता है कि आप एक बिना-स्टड कंटेनर द्वारा एसटीडी कंटेनर को अधिक आसानी से स्थानापन्न कर सकते हैं और अंतर्निहित कंटेनर प्रकार आपके कोड के लिए अधिक पारदर्शी होता है। कंटेनर कार्यान्वयन की एक बहुत व्यापक विविधता का समर्थन करता है।

लेकिन निश्चित रूप से यह हमेशा सही ढंग से वेट किया जाना चाहिए और ओवरस्ट्रेक्शन भी अच्छा नहीं है। हालांकि फ्री फ़ंक्शंस का उपयोग करना ओवर-एब्सट्रेक्शन का अधिक हिस्सा नहीं है, लेकिन फिर भी यह C ++ 03 कोड के साथ संगतता को तोड़ता है, जो कि C ++ 11 की इस छोटी उम्र में अभी भी आपके लिए एक मुद्दा हो सकता है।


3
C ++ 03 में, आप सिर्फ boost::begin()/ का उपयोग कर सकते हैं end(), इसलिए कोई वास्तविक असंगति नहीं है :)
मार्क म्यूत्ज़ - mmutz

1
@ MarcMutz-mmutz खैर, बढ़ावा निर्भरता हमेशा एक विकल्प नहीं है (और केवल इस्तेमाल के लिए इस्तेमाल किया जाता है, तो यह काफी overkill है begin/end)। तो मुझे लगता है कि शुद्ध C ++ 03 के लिए भी एक असंगतता होगी। लेकिन जैसा कहा गया है, यह एक छोटी (और छोटी हो रही) असंगति है, जैसा कि C ++ 11 (कम से कम begin/endविशेष रूप से) वैसे भी अधिक से अधिक गोद लेना है।
ईसाई राऊ

0

अंततः लाभ कोड में है जो सामान्यीकृत है जैसे कि यह कंटेनर अज्ञेयवादी है। यह std::vectorकोड के परिवर्तन के बिना एक , एक सरणी या एक सीमा पर काम कर सकता है।

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

अधिक विस्तार के लिए यहां देखें ।

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