कार्यान्वयन को लीक किए बिना एक आंतरिक वेक्टर के पुनरावृत्ति की अनुमति दें


32

मेरे पास एक वर्ग है जो लोगों की सूची का प्रतिनिधित्व करता है।

class AddressBook
{
public:
  AddressBook();

private:
  std::vector<People> people;
}

मैं ग्राहकों को लोगों के वेक्टर पर पुनरावृति करने की अनुमति देना चाहता हूं। पहले सोचा था कि मैं बस था:

std::vector<People> & getPeople { return people; }

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

आंतरिक लीक किए बिना पुनरावृत्ति की अनुमति देने का सबसे अच्छा तरीका क्या है?


2
सबसे पहले, यदि आप नियंत्रण बनाए रखना चाहते हैं, तो आपको अपने वेक्टर को एक संदर्भ के रूप में वापस करना चाहिए। आप अभी भी उस तरह से कार्यान्वयन विवरणों को उजागर करेंगे, इसलिए मैं आपकी कक्षा को चलने योग्य बनाने की सलाह देता हूं और कभी भी आपकी डेटा संरचना को उजागर नहीं कर सकता (हो सकता है कि यह कल एक हैश तालिका हो?)।
मूढ़

एक त्वरित Google खोज ने मुझे यह उदाहरण दिया: sourcemaking.com/design_patterns/Iterator/cpp/1
Doc Brown

1
@DocBrown का कहना है कि उचित समाधान की संभावना है - व्यवहार में इसका मतलब है कि आप अपने एड्रेसबुक क्लास को एक शुरुआत () और समाप्ति () विधि (प्लस कांस्ट ओवरलोड और अंततः भी cbegin / cend) देते हैं जो बस वेक्टर की शुरुआत () और अंत लौटाते हैं ( )। ऐसा करने से आपकी कक्षा भी सभी सबसे अनुसूचित जनजाति वर्ग के लिए उपयोगी होगी।
४२

1
@stijn यह एक जवाब होना चाहिए, टिप्पणी नहीं :-)
फिलिप केंडल

1
@stijn नहीं, वह नहीं है जो DocBrown और जुड़ा हुआ लेख कहता है। सही समाधान स्थिति को इंगित करने के लिए एक सुरक्षित तंत्र के साथ कंटेनर वर्ग की ओर इशारा करते हुए एक प्रॉक्सी वर्ग का उपयोग करना है। वेक्टर की वापसी begin()और end()खतरनाक हैं क्योंकि (1) वे प्रकार वेक्टर पुनरावृत्तियां (कक्षाएं) हैं जो एक को दूसरे कंटेनर में स्विच करने से रोकता है जैसे कि ए set। (2) यदि वेक्टर को संशोधित किया जाता है (जैसे कि उगाया गया या कुछ आइटम मिटा दिया जाता है), तो वेक्टर वेक्टर के कुछ या सभी को अमान्य किया जा सकता है।
rwong

जवाबों:


25

इंटर्नल को लीक किए बिना पुनरावृत्ति की अनुमति देना ठीक वैसा ही है जैसा कि इटरेटर पैटर्न वादा करता है। बेशक यह मुख्य रूप से सिद्धांत है इसलिए यहां एक व्यावहारिक उदाहरण है:

class AddressBook
{
  using peoples_t = std::vector<People>;
public:
  using iterator = peoples_t::iterator;
  using const_iterator = peoples_t::const_iterator;

  AddressBook();

  iterator begin() { return people.begin(); }
  iterator end() { return people.end(); }
  const_iterator begin() const { return people.begin(); }
  const_iterator end() const { return people.end(); }
  const_iterator cbegin() const { return people.cbegin(); }
  const_iterator cend() const { return people.cend(); }

private:
  peoples_t people;
};

आप मानक beginऔर endविधियाँ प्रदान करते हैं , एसटीएल में अनुक्रम की तरह और वेक्टर की विधि को अग्रेषित करके उन्हें लागू करते हैं। यह कुछ कार्यान्वयन विवरण को लीक करता है अर्थात आप एक वेक्टर पुनरावर्तक लौटा रहे हैं लेकिन कोई भी समझदार ग्राहक कभी उस पर निर्भर नहीं होना चाहिए, इसलिए यह चिंता का विषय नहीं है। मैंने सभी ओवरलोड यहां दिखाए हैं, लेकिन निश्चित रूप से आप केवल कॉन्स्ट संस्करण प्रदान करके शुरू कर सकते हैं यदि क्लाइंट को किसी भी व्यक्ति की प्रविष्टियों को बदलने में सक्षम नहीं होना चाहिए। मानक नामकरण के उपयोग से लाभ हैं: किसी को भी कोड को पढ़ने से तुरंत पता चल जाता है कि यह 'मानक' पुनरावृत्ति प्रदान करता है और जैसे कि सभी सामान्य एल्गोरिदम के साथ काम करता है, लूप के लिए आधारित सीमा आदि।


नोट: हालांकि यह निश्चित रूप से काम करता है और स्वीकार किया जाता है कि यह रवांग की टिप्पणी पर ध्यान देने योग्य है: सदिश के पुनरावृत्तियों के इर्द-गिर्द एक अतिरिक्त आवरण / छद्म जोड़ने से ग्राहक वास्तविक अंतर्निहित
पुनरावृत्ति से

इसके अलावा, कृपया ध्यान दें कि एक प्रदान करने begin()और end()है कि बस वेक्टर के लिए आगे begin()और end()वेक्टर अपने आप में तत्वों को संशोधित करने के लिए अनुमति देता है, हो सकता है का उपयोग कर std::sort()। इस बात पर निर्भर करता है कि आप किन आक्रमणकारियों को संरक्षित करने की कोशिश कर रहे हैं, यह स्वीकार्य हो भी सकता है और नहीं भी। प्रदान करना begin()और end(), हालांकि, छोरों के लिए C ++ 11 रेंज-आधारित का समर्थन करना आवश्यक है।
पैट्रिक निदेज़ेल्स्की

C ++ 14 का उपयोग करते समय आपको संभवतः ऑटो का उपयोग करते हुए समान प्रकार के पुनरावृत्त फ़ंक्शन के समान कोड भी दिखाना चाहिए।
क्लेम

यह कार्यान्वयन विवरण को कैसे छिपा रहा है?
B:овиЈ

@ B @овиЈ पूर्ण वेक्टर को उजागर नहीं करने से - छिपाने का मतलब यह नहीं है कि कार्यान्वयन का शाब्दिक अर्थ है हेडर से छिपाया जाना और स्रोत फ़ाइल में डाला जाना चाहिए: अगर यह निजी क्लाइंट इसे वैसे भी एक्सेस नहीं कर सकता है
9'14 को stijn

4

यदि पुनरावृत्ति आप की जरूरत है, तो शायद चारों ओर एक आवरण std::for_eachपर्याप्त होगा:

class AddressBook
{
public:
  AddressBook();

  template <class F>
  void for_each(F f) const
  {
    std::for_each(begin(people), end(people), f);
  }

private:
  std::vector<People> people;
};

यह शायद cbegin / cend के साथ एक कांस्टिट्यूशन लागू करने के लिए बेहतर होगा। लेकिन यह समाधान अंतर्निहित कंटेनर तक पहुंच देने से कहीं बेहतर है।
गैलोप

@ galop1n यह एक पुनरावृत्ति को लागू करता हैconstfor_each()एक है constसदस्य कार्य करते हैं। इसलिए, सदस्य के peopleरूप में देखा जाता है const। इसलिए, begin()और end()अधिक भार होगा const। इसलिए, वे वापस आ जाएगी const_iteratorकरने के लिए रों people। इसलिए, f()एक प्राप्त होगा People const&। लेखन cbegin()/ cend()यहां कुछ भी नहीं बदलेगा, व्यवहार में, हालांकि constमैं के एक जुनूनी उपयोगकर्ता के रूप में तर्क दे सकता है कि यह अभी भी करने योग्य है, जैसा कि (ए) क्यों नहीं; यह सिर्फ 2 वर्ण है, (b) मुझे यह कहना पसंद है कि मेरा क्या मतलब है, कम से कम const(सी) यह गलती से गैर-चिपकाने के खिलाफ गार्ड है const, आदि
underscore_d

3

आप pimpl मुहावरे का उपयोग कर सकते हैं , और कंटेनर पर पुनरावृति करने के तरीके प्रदान कर सकते हैं ।

हेडर में:

typedef People* PeopleIt;

class AddressBook
{
public:
  AddressBook();


  PeopleIt begin();
  PeopleIt begin() const;
  PeopleIt end();
  PeopleIt end() const;

private:
  struct Imp;
  std::unique_ptr<Imp> pimpl;
};

स्रोत में:

struct AddressBook::Imp
{
  std::vector<People> people;
};

PeopleIt AddressBook::begin()
{
  return &pimpl->people[0];
}

इस तरह, यदि आपका क्लाइंट हेडर से टाइपडिफ का उपयोग करता है, तो वे ध्यान नहीं देंगे कि आप किस प्रकार के कंटेनर का उपयोग कर रहे हैं। और कार्यान्वयन विवरण पूरी तरह से छिपा हुआ है।


1
यह सही है ... पूर्ण कार्यान्वयन छिपाना और कोई अतिरिक्त उपरि नहीं।
अमूर्तता ही सब कुछ है।

2
@Abstractioniseverything। " कोई अतिरिक्त उपरि नहीं " स्पष्ट रूप से गलत है। PImpl हर आवृत्ति के लिए एक गतिशील मेमोरी आवंटन (और, बाद में, मुफ्त) और इसके माध्यम से जाने वाली प्रत्येक विधि के लिए एक सूचक अप्रत्यक्ष (कम से कम 1) जोड़ता है। चाहे वह किसी भी स्थिति के लिए ज्यादा ओवरहेड हो, बेंचमार्किंग / प्रोफाइलिंग पर निर्भर करता है, और कई मामलों में यह शायद पूरी तरह से ठीक है, लेकिन यह बिल्कुल सच नहीं है - और मुझे लगता है कि गैर जिम्मेदाराना - यह घोषित करने के लिए कि यह कोई ओवरहेड नहीं है।
अंडरस्कोर_ड

@underscore_d मैं सहमत हूं; वहां गैर-जिम्मेदार होने का मतलब नहीं है, लेकिन, मुझे लगता है कि मैं इस संदर्भ का शिकार हो गया हूं। "कोई अतिरिक्त उपरि नहीं ..." तकनीकी रूप से गलत है, जैसा कि आपने चतुराई से बताया है; माफी ...
अमूर्तता सब कुछ है।

1

एक सदस्य कार्य प्रदान कर सकता है:

size_t Count() const
People& Get(size_t i)

जो कार्यान्वयन विवरण (जैसे कि आकस्मिकता) को उजागर किए बिना पहुंच की अनुमति देते हैं और एक इट्रेटर वर्ग के भीतर इनका उपयोग करते हैं:

class Iterator
{
    AddressBook* addressBook_;
    size_t index_;

public:
    Iterator(AddressBook& addressBook, size_t index=0) 
    : addressBook_(&addressBook), index_(index) {}

    People& operator*()
    {
        return addressBook_->Get(index_);
    }

    Iterator& operator ++ ()
    {
       ++index_;
       return *this;
    }

    bool operator != (const Iterator& i) const
    {
        assert(addressBook_ == i.addressBook_);
        return index_ != i.index_;
    }
};

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

AddressBook::Iterator AddressBook::begin()
{
    return Iterator(this);
}

AddressBook::Iterator AddressBook::end()
{
    return Iterator(this, Count());
}

आप शायद बाहर आदि तत्वों के साथ पुनरावृत्त वर्ग को मांस की आवश्यकता होगी, लेकिन मुझे लगता है कि यह वही होगा जो आपने पूछा है।


1

यदि आप std :: वेक्टर से कार्यों का सटीक कार्यान्वयन चाहते हैं, तो नीचे के रूप में निजी विरासत का उपयोग करें और जो उजागर हो उसे नियंत्रित करें।

template <typename T>
class myvec : private std::vector<T>
{
public:
    using std::vector<T>::begin;
    using std::vector<T>::end;
    using std::vector<T>::push_back;
};

संपादित करें: यदि आप आंतरिक डेटा संरचना यानी std :: वेक्टर को भी छिपाना चाहते हैं तो इसका पुन: प्रकाशन नहीं किया जाता है


ऐसी स्थिति में वंशानुक्रम सर्वोत्तम रूप से बहुत आलसी होता है (आपको रचना का उपयोग करना चाहिए और अग्रेषण विधियाँ प्रदान करनी चाहिए, विशेष रूप से क्योंकि यहाँ आगे करने के लिए बहुत कम हैं), अक्सर भ्रामक और असुविधाजनक होता है (यदि आप अपने स्वयं के तरीकों को जोड़ना चाहते हैं तो vectorलोगों के साथ संघर्ष करना चाहिए) जिसे आप कभी भी उपयोग नहीं करना चाहते हैं, लेकिन फिर भी इनहेरिट करना चाहिए?), और शायद सक्रिय रूप से खतरनाक है (क्या होगा अगर क्लास से आलसी विरासत में मिला हो, एक पॉइंटर के माध्यम से उस आधार प्रकार में कहीं नष्ट हो सकता है, लेकिन यह [गैर जिम्मेदाराना] विनाश से बचाता नहीं था) इस तरह के पॉइंटर के माध्यम से व्युत्पन्न ओब्ज, इसलिए बस इसे नष्ट करना यूबी है?)
अंडरस्कोर_ड
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.