বাস্তবায়ন ফাঁস না করে কোনও অভ্যন্তরীণ ভেক্টরের পুনরাবৃত্তির অনুমতি দিন


32

আমার একটি ক্লাস রয়েছে যা লোকের একটি তালিকা উপস্থাপন করে।

class AddressBook
{
public:
  AddressBook();

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

আমি ক্লায়েন্টদের লোকদের ভেক্টর দিয়ে পুনরাবৃত্তি করতে দিতে চাই। আমার প্রথম চিন্তাটি সহজভাবে ছিল:

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

যাহোক, আমি ক্লায়েন্টের কাছে প্রয়োগের বিশদটি ফাঁস করতে চাই না । ভেক্টরটি সংশোধন করা হলে আমি কিছু নির্দিষ্ট আক্রমণকারী বজায় রাখতে চাই এবং যখন আমি প্রয়োগটি ফাঁস করি তখন আমি এই আক্রমণকারীদের উপর নিয়ন্ত্রণ হারাতে পারি।

ইন্টার্নালগুলি ফাঁস না করে পুনরাবৃত্তির অনুমতি দেওয়ার সর্বোত্তম উপায় কী?


2
প্রথমত, আপনি যদি নিয়ন্ত্রণ বজায় রাখতে চান তবে আপনার ভেক্টরকে কনস্টের রেফারেন্স হিসাবে ফিরিয়ে দেওয়া উচিত। আপনি এখনও বাস্তবায়নের বিশদটি সেভাবেই উন্মোচিত করতে চান, তাই আমি আপনার শ্রেণিকে পুনরাবৃত্ত করার এবং আপনার ডেটা কাঠামোটি কখনও প্রকাশ না করার পরামর্শ দিই (সম্ভবত এটি আগামীকাল হ্যাশ টেবিল হবে?)।
মূর্তি

একটি তাত্ক্ষণিক গুগল অনুসন্ধান আমাকে এই উদাহরণটি প্রকাশ করেছে: সোর্স মেকিং
ডক ব্রাউন

1
@ ডকব্রাউন যা বলছেন সম্ভবত এটিই উপযুক্ত সমাধান - বাস্তবে এর অর্থ আপনি আপনার অ্যাড্রেসবুক ক্লাসটিকে একটি শুরু () এবং শেষ () পদ্ধতি (প্লাস কনস্ট ওভারলোডস এবং শেষ পর্যন্ত সিবেগিন / সেন্ট) প্রদান করেন যা কেবল ভেক্টরের শুরু () এবং শেষ ( )। এটি করার মাধ্যমে আপনার ক্লাসটি সমস্ত স্ট্যান্ড অ্যালগরিদমের দ্বারাও ব্যবহারযোগ্য হবে।
stijn

1
@ স্টিজন এটি উত্তর হওয়া উচিত, কোনও মন্তব্য নয় :-)
ফিলিপ কেন্ডাল

1
@ স্টিজন না, ডকব্রাউন এবং লিঙ্কযুক্ত নিবন্ধ যা বলে তা নয়। সঠিক সমাধান হ'ল অবস্থান নির্দেশ করার জন্য নিরাপদ প্রক্রিয়া সহ ধারক শ্রেণীর দিকে ইঙ্গিত করে একটি প্রক্সি ক্লাস ব্যবহার করা। ভেক্টরের ফিরে আসা begin()এবং end()বিপজ্জনক কারণ (1) এই ধরণেরগুলি হ'ল ভেক্টর পুনরাবৃত্তকারী (শ্রেণি) যা একজনকে অন্য ধারক যেমন স এর থেকে স্যুইচ করা থেকে বাধা দেয় set। (২) যদি ভেক্টরটি সংশোধন করা হয় (যেমন উত্থিত বা কিছু আইটেম মুছে ফেলা হয়) তবে কিছু বা সমস্ত ভেক্টর পুনরায় অবৈধ করা যেতে পারে।
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পদ্ধতিগুলি সরবরাহ করেন এবং ভেক্টরের পদ্ধতিতে ফরোয়ার্ড করে কেবল এগুলি প্রয়োগ করেন। এটি কিছু বাস্তবায়ন বিশদটি ফাঁস করে দেয় যে আপনি কোনও ভেক্টর পুনরায় ফিরিয়ে দিচ্ছেন তবে কোনও বুদ্ধিমান ক্লায়েন্টের উপর নির্ভর করা উচিত নয় যাতে এটি কোনও উদ্বেগ নয়। আমি এখানে সমস্ত ওভারলোড দেখিয়েছি তবে অবশ্যই ক্লায়েন্টদের কোনও লোকের এন্ট্রি পরিবর্তন করতে না পারলে কেবল কনস্ট ভার্সন সরবরাহ করে আপনি শুরু করতে পারেন। স্ট্যান্ডার্ড নামকরণ ব্যবহারের সুবিধাগুলি রয়েছে: যে কেউ কোডটি পড়ে তাৎক্ষণিকভাবে জানে যে এটি 'স্ট্যান্ডার্ড' পুনরাবৃত্তি সরবরাহ করে এবং যেমন সমস্ত সাধারণ অ্যালগরিদমের সাথে কাজ করে, লুপগুলির উপর ভিত্তি করে পরিসর ইত্যাদি works


দ্রষ্টব্য: যদিও এটি অবশ্যই কাজ করে এবং স্বীকৃত হয় যে প্রশ্নে রওয়ংয়ের মন্তব্যগুলি নোট করা মূল্যবান: এখানে ভেক্টরের পুনরাবৃত্তকারীগুলির চারপাশে একটি অতিরিক্ত মোড়ক / প্রক্সি যুক্ত করা ক্লায়েন্টকে প্রকৃত অন্তর্নিহিত পুনরুক্তকারী থেকে স্বাধীন করে
তুলবে

উপরন্তু, নোট করে একটি প্রদানের begin()এবং end()যে শুধু ভেক্টর এর অপেক্ষায় begin()এবং end()ভেক্টর নিজেই উপাদানের পরিবর্তন করতে ব্যবহারকারী পারবেন, হয়তো ব্যবহার std::sort()। আপনি কী আক্রমণকারীদের সংরক্ষণের চেষ্টা করছেন তার উপর নির্ভর করে এটি গ্রহণযোগ্য হতে পারে এবং নাও পারে। সরবরাহ করা begin()এবং end(), যদিও লুপগুলির জন্য C ++ 11 রেঞ্জ-ভিত্তিক সমর্থন করা প্রয়োজন।
প্যাট্রিক নিডজিয়েলস্কি

সি ++ 14 ব্যবহার করার সময় আপনার সম্ভবত অটো ব্যবহার করে পুনরায় প্রকারের পুনরুদ্ধার ফাংশন হিসাবে একই কোডটি দেখানো উচিত।
ক্লাইম

এটি কীভাবে বাস্তবায়নের বিশদটি গোপন করছে?
B

@ BЈовић সম্পূর্ণ ভেক্টর প্রকাশক না দ্বারা - গোপন অগত্যা মানে এই নয় বাস্তবায়ন আক্ষরিক একটি শিরোলেখ থেকে লুকানো এবং সোর্স ফাইল রাখা হবে: যদি এটি ব্যক্তিগত ক্লায়েন্টের তবুও এটি অ্যাক্সেস করতে পারছি না
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

@ galop1n এটি একটি পুনরাবৃত্তি প্রয়োগ করেconstfor_each()একটি হল constসদস্য ফাংশন। সুতরাং, সদস্য peopleহিসাবে দেখা হয় const। সুতরাং, begin()এবং end()হিসাবে ওভারলোড হবে const। সুতরাং, তারা ফিরে const_iteratorআসবে people। সুতরাং, f()একটি পাবেন People const&। লেখালেখি cbegin()/ cend()এখানে কিছুই প্রয়োগ করা যাবে না, বাস্তবে, যদিও constআমি একজন আবেশী ব্যবহারকারী হিসাবে এটি যুক্তিযুক্ত হতে পারে তবুও (ক) কেন নয়; এটি মাত্র ২ টি অক্ষর, (খ) আমি যা বলতে চাইছি তা বলতে চাই, কমপক্ষে const, (গ) এটি দুর্ঘটনাক্রমে কোথাও নন constইত্যাদি আটকানো থেকে রক্ষা করে
আন্ডারস্কোর_ডে

3

আপনি পিম্পল আইডিয়মটি ব্যবহার করতে পারেন এবং ধারকটির উপরে পুনরাবৃত্তি করার জন্য পদ্ধতি সরবরাহ করতে পারেন।

শিরোনামে:

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। " অতিরিক্ত অতিরিক্ত ওভারহেড " স্পষ্টতই মিথ্যা। পিআইএমপিএল প্রতিটি উদাহরণের জন্য একটি গতিশীল মেমরি বরাদ্দ (এবং, পরে বিনামূল্যে) এবং এর মধ্য দিয়ে যাওয়া প্রতিটি পদ্ধতির জন্য একটি পয়েন্টার ইন্ডিরেশন (কমপক্ষে 1) যোগ করে। যে কোনও পরিস্থিতির জন্য এটি অনেক বেশি ওভারহেড কিনা তা বেঞ্চমার্কিং / প্রোফাইলিংয়ের উপর নির্ভর করে এবং অনেক ক্ষেত্রে এটি সম্ভবত পুরোপুরি ঠিক আছে তবে এটি একেবারেই সত্য নয় - এবং আমি বরং এটি দায়িত্বজ্ঞানহীন বলে মনে করি - এটির কোনও ওভারহেড নেই বলে ঘোষণা করে ।
আন্ডারস্কোর_১১

@ আসরে_আমি সম্মত; সেখানে দায়িত্বজ্ঞানহীন হওয়ার অর্থ নয়, তবে, আমি অনুমান করি যে আমি প্রসঙ্গটির শিকার হয়েছি। "অতিরিক্ত অতিরিক্ত ওভারহেড নেই ..." প্রযুক্তিগতভাবে ভুল নয়, আপনি যথাযথভাবে উল্লেখ করেছেন; কৈফিয়ত ...
বিমূর্ততা সবকিছু।

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_;
    }
};

ইটারেটরগুলি নিম্নলিখিত ঠিকানা বইয়ের মাধ্যমে ফিরে আসতে পারে:

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

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

আপনার সম্ভবত পুনরাবৃত্ত শ্রেণীর বৈশিষ্ট্য ইত্যাদি দিয়ে বেরিয়ে আসতে হবে তবে আমি মনে করি যে এটি আপনি যা বলেছিলেন তা করবে।


1

আপনি যদি স্টাড :: ভেক্টর থেকে ফাংশনগুলির যথাযথ প্রয়োগ করতে চান তবে নীচের হিসাবে ব্যক্তিগত উত্তরাধিকার ব্যবহার করুন এবং কী উদ্ভাসিত হয়েছে তা নিয়ন্ত্রণ করুন।

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, যা আপনি কখনই ব্যবহার করতে চান না তবে অবশ্যই উত্তরাধিকার সূত্রে আবশ্যক?), এবং সম্ভবত সক্রিয়ভাবে বিপজ্জনক (যদি শ্রেণিটি অলসভাবে উত্তরাধিকার সূত্রে প্রাপ্ত হয় তবে কোথাও কোথাও সেই বেস প্রকারের পয়েন্টারের মাধ্যমে মুছে ফেলা যায়, তবে এটি [দায়িত্বজ্ঞানহীনভাবে] ধ্বংসের বিরুদ্ধে রক্ষা করেনি এই জাতীয় পয়েন্টারের মাধ্যমে একটি উত্সাহিত আপত্তি, সুতরাং কেবল এটি ধ্বংস করা ইউবি?)
আন্ডারস্কোর_১
আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.