Uygulama sızıntısı olmadan dahili bir vektörün yinelenmesine izin ver


32

İnsanların listesini temsil eden bir sınıfım var.

class AddressBook
{
public:
  AddressBook();

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

Müşterilerin insan vektörünü tekrar etmelerine izin vermek istiyorum. Sahip olduğum ilk düşünce basitti:

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

Ancak, uygulama detaylarını müşteriye sızdırmak istemiyorum . Vektör değiştirilirken bazı değişmezleri korumak isteyebilirim ve uygulamayı sızdırdığımda bu değişmezler üzerindeki kontrolümü kaybedebilirim.

İnternal sızıntı yapmadan yinelemeye izin vermenin en iyi yolu nedir?


2
Öncelikle, kontrolü sürdürmek istiyorsanız, vektörünüzü const referansı olarak geri göndermelisiniz. Uygulama detaylarını bu şekilde açıklamaya devam edersiniz, bu yüzden sınıfınızı yinelenebilir hale getirmenizi ve veri yapınızı asla göstermemenizi öneririm (belki de yarın bir karma tablo olur?).
Idoby

Hızlı bir google araması bana bu örneği gösterdi: sourcemaking.com/design_patterns/Iterator/cpp/1
Doc Brown

1
@DocBrown’un ​​söylediği şey muhtemelen uygun bir çözüm - pratikte bu, AddressBook sınıfınıza bir vektör () ve end () ekleri (artı const aşırı ve en sonunda da cbegin / cend) vermeniz anlamına gelir; ). Bunu yaparak, sınıfınız tüm std algoritmaları tarafından da kullanılabilir.
stijn

1
@stijn Bu bir cevap olmalı, yorum değil :-)
Philip Kendall

1
@stijn Hayır, DocBrown ve bağlantılı makalenin söylediği bu değil. Doğru çözüm, konum belirtmek için güvenli bir mekanizma ile birlikte konteyner sınıfına işaret eden bir proxy sınıfı kullanmaktır. Vektörlerin döndürülmesi begin()ve end()tehlikelidir, çünkü (1) bu türler, birinin a gibi başka bir kaba geçmesini önleyen vektör yineleyicilerdir (sınıflar) set. (2) Vektör değiştirilirse (örneğin, büyümüş veya silinmiş bazı öğeler), vektör yineleyicilerin bazıları veya tamamı geçersiz olabilir.
rwong

Yanıtlar:


25

İnternal sızıntı yapmadan yinelemeye izin ver , yineleyici kalıbın tam olarak vaat ettiği şeydir. Tabii ki esas olarak teori bu yüzden pratik bir örnek:

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

STL'deki diziler gibi standart beginve endyöntemler sağlarsınız ve bunları vektör yöntemine ileterek uygularsınız. Bu, bir vektör yineleyiciyi döndürdüğünüz için bazı uygulama detaylarını sızdırıyor ancak aklı başında hiçbir müşteri buna bağlı olmamalı, bu yüzden bir endişe değil. Burada tüm aşırı yükleri gösterdim ancak elbette eğer müşterilerin herhangi bir Kişi girişini değiştirmemesi gerekiyorsa, sadece const sürümünü vererek başlayabilirsiniz. Standart isimlendirmenin faydaları vardır: kodu okuyan herkes derhal 'standart' yineleme sağladığını bilir ve bu şekilde tüm ortak algoritmalarla çalışır, döngülere dayalı aralık vb.


not: bu kesinlikle işe yarıyor ve kabul edilse de, bu soruya rwong'un yorumuna dikkat etmek gerekiyor: vektörün yineleyicilerinin etrafına fazladan sarıcı / proxy eklemek, müşterileri asıl yineleyiciden bağımsız hale getirir
stijn

Ek olarak, bir vektör begin()ve end()sadece ileriye dönük olan begin()ve end()kullanıcının belki de vektör içindeki öğeleri değiştirmesine izin verdiğine dikkat edin std::sort(). Hangi değişmezleri korumaya çalıştığınıza bağlı olarak, bu kabul edilebilir olabilir veya olmayabilir. Sağlamak begin()ve end()olsa da, döngüleri için C ++ 11 aralığı tabanlı desteklemek için gereklidir.
Patrick Niedzielski

Muhtemelen aynı kodu, C ++ 14 kullanırken yineleyici işlevlerinin dönüş tipleri olarak auto kullanarak da göstermelisiniz.
Klaim

Bu uygulama detaylarını nasıl saklıyor?
BЈовић

@ BЈовић tam vektör teşhir etmeyerek - gizleme mutlaka uygulanması tam anlamıyla bir başlığından gizli ve kaynak dosyada koymak zorundadır anlamına gelmez: o özel istemci eğer yine de erişemez
stijn

4

İhtiyacınız olan tek şey yineleme ise, belki etrafında bir sarmalayıcı std::for_eachyeterli olacaktır:

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 ile bir const yineleme uygulamak muhtemelen daha iyi olur. Ancak bu çözüm, alttaki konteynere erişim vermekten çok daha iyidir.
galop1n

galop1n @ O does bir zorlamak constiterasyon. for_each()Bir olan constüye fonksiyonu. Dolayısıyla, üye peopleolarak görülür const. Dolayısıyla, begin()ve end()olarak aşırı yüklenir const. Dolayısıyla, const_iterators'ye dönecekler people. Dolayısıyla, bir f()alacaksınız People const&. Yazma cbegin()/ cend()ait olsa saplantılı kullanıcı olarak, pratikte, hiçbir şey değişecek burada constben (a), hala yapmaya değer neden olmasın iddia edebilir; bu sadece 2 karakter, (b) En azından const, (c) ' nin neyi kastettiğimi söylemekten hoşlanmam , (c) yanlışlıkla başka bir yere const, vb. yapıştırılmasını
underscore_d

3

Pimpl deyimini kullanabilir ve kabın üzerinde yineleme yapmak için yöntemler kullanabilirsiniz.

Başlıkta:

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

Kaynakta:

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

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

Bu şekilde, eğer müşteriniz başlıktan gelen typedef kullanıyorsa, ne tür bir konteyner kullandığınızı fark etmeyeceklerdir. Ve uygulama detayları tamamen gizlidir.


1
Bu DOĞRU ... tam uygulama gizleme ve ek yükü yok.
Soyutlama her şeydir.

2
@Abstractioniseverything. " ek yükü yok " açıkça yanlıştır. PImpl her örnek için dinamik bellek ayırma (ve daha sonra ücretsiz) ve üzerinden geçen her yöntem için işaretçi indirme (en az 1) ekler. Herhangi bir durum için bunun çok fazla yükü olup olmadığı kıyaslama / profillemeye bağlıdır ve çoğu durumda muhtemelen tamamen iyidir, ancak kesinlikle doğru değildir - ve bunun sorumsuz olduğunu düşünüyorum - ek yükü olmadığını beyan etmek.
underscore_d

@underscore_d Katılıyorum; orada sorumsuz olmak demek değil, sanırım bağlamı avladım. “Ek yük yok ...” teknik olarak yanlıştır; Özür dilerim ...
Soyutlama her şeydir.

1

Biri üye işlevleri sağlayabilir:

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

Uygulama ayrıntılarını (bitişiklik gibi) göstermeden erişime izin veren ve bunları bir yineleyici sınıfında kullananlar:

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

Tekrarlayıcılar adres defterine aşağıdaki şekilde gönderilebilir:

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

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

Muhtemelen yineleyici sınıfını özellikler vb. İle çözmeniz gerekir, ancak bunun istediğiniz şeyi yapacağını düşünüyorum.


1

İşlevlerin std :: vector öğesinden tam olarak uygulanmasını istiyorsanız, aşağıdaki gibi özel miras kullanın ve maruz kaldıklarını kontrol edin.

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

Düzenleme: Dahili veri yapısını, örneğin std :: vector de gizlemek istiyorsanız, bu önerilmez.


Böyle bir durumda kalıtım en çok tembeldir (kompozisyonu kullanmalı ve yönlendirme yöntemleri sağlamalısınız, özellikle burada çok az olduğu için), kafa karıştırıcı ve sakıncalı (kendisiyle çelişen kendi yöntemlerinizi eklemek istiyorsanız vector, hangisini kullanmak istemediğiniz, ancak yine de miras almanız gerekir?) ve belki de tehlikeli (ya da tembel olarak miras kalan sınıf bir yere o işarete göre bir işaretçi ile silinebilirse, ancak [sorumsuzca] imhasına karşı koruma sağlamazsa böyle bir işaretçi yoluyla türetilmiş bir nesneyi, basitçe onu imha etmek UB?)
underscore_d
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.