Mam klasę reprezentującą listę osób.
class AddressBook
{
public:
AddressBook();
private:
std::vector<People> people;
}
Chcę pozwolić klientom na iterację po wektorze ludzi. Pierwszą myślą, którą miałem, było po prostu:
std::vector<People> & getPeople { return people; }
Nie chcę jednak ujawniać klientowi szczegółów implementacji . Mogę chcieć zachować pewne niezmienniki, gdy wektor jest modyfikowany, i tracę kontrolę nad tymi niezmiennikami, gdy wyciekam z implementacji.
Jaki jest najlepszy sposób na iterację bez wycieku elementów wewnętrznych?
begin()
iend()
jest niebezpieczne, ponieważ (1) te typy są iteratorami (klasami) wektorów, które uniemożliwiają przejście do innego kontenera, takiego jak aset
. (2) Jeśli wektor zostanie zmodyfikowany (np. Wyhodowany lub niektóre elementy usunięte), niektóre lub wszystkie iteratory wektora mogły zostać unieważnione.Odpowiedzi:
zezwolenie na iterację bez wycieku elementów wewnętrznych jest dokładnie tym, co obiecuje wzór iteratora. Oczywiście jest to głównie teoria, więc oto praktyczny przykład:
Podajesz standard
begin
iend
metody, podobnie jak sekwencje w STL i implementujesz je po prostu przekazując do metody wektorowej. To przecieka niektóre szczegóły implementacji, a mianowicie, że zwracasz iterator wektorowy, ale żaden rozsądny klient nigdy nie powinien na tym polegać, więc nie jest to problemem. Pokazałem tutaj wszystkie przeciążenia, ale oczywiście możesz zacząć od podania stałej wersji, jeśli klienci nie będą mogli zmieniać żadnych wpisów People. Korzystanie ze standardowego nazewnictwa ma zalety: każdy, kto czyta kod, od razu wie, że zapewnia „standardową” iterację i jako taki działa ze wszystkimi popularnymi algorytmami, zasięgiem opartym na pętlach itp.źródło
begin()
iend()
to tylko do przodu do wektorabegin()
iend()
pozwala użytkownikowi modyfikować elementy w samym wektorze, być może przy użyciustd::sort()
. W zależności od niezmienników, które próbujesz zachować, może to być akceptowalne. Zapewnieniebegin()
iend()
, choć, jest konieczne do obsługi pętli w zakresie C ++ 11.Jeśli iteracja jest wszystko, czego potrzeba, to może owinięcie wokół
std::for_each
wystarczy:źródło
const
iteracji. Jestfor_each()
toconst
funkcja członka. W związku z tym członekpeople
jest postrzegany jakoconst
. Stądbegin()
iend()
będzie przeciążać jakconst
. Dlatego powrócąconst_iterator
dopeople
. W związku zf()
tym otrzymaPeople const&
. Pisaniecbegin()
/cend()
tutaj nic nie zmieni, w praktyce, chociaż jako obsesyjny użytkownik,const
mogę argumentować, że nadal warto to robić, ponieważ (a) dlaczego nie; to tylko 2 znaki, (b) lubię mówić, co mam na myśli, przynajmniej zconst
, (c) chroni przed przypadkowym wklejeniem gdzie indziejconst
, itp.Możesz użyć idiomu pimpl i podać metody iteracji po kontenerze.
W nagłówku:
W źródle:
W ten sposób, jeśli twój klient użyje typedef z nagłówka, nie zauważy, jakiego rodzaju kontenera używasz. A szczegóły implementacji są całkowicie ukryte.
źródło
Można zapewnić funkcje członkowskie:
Które umożliwiają dostęp bez ujawniania szczegółów implementacji (takich jak ciągłość) i wykorzystują je w klasie iteratora:
Iteratory mogą być następnie zwrócone przez książkę adresową w następujący sposób:
Prawdopodobnie będziesz musiał uzupełnić klasę iteratora o cechy itp., Ale myślę, że spełni to, o co prosiłeś.
źródło
jeśli chcesz dokładnej implementacji funkcji ze std :: vector, użyj prywatnego dziedziczenia jak poniżej i kontroluj, co jest widoczne.
Edycja: Nie jest to zalecane, jeśli chcesz również ukryć wewnętrzną strukturę danych, tj. Std :: vector
źródło
vector
, którego nigdy nie chcesz używać, ale mimo to musisz odziedziczyć?), a może być aktywnie niebezpieczny (co jeśli leniwie odziedziczona klasa mogłaby zostać usunięta przez wskaźnik do tego typu bazy gdzieś, ale [nieodpowiedzialnie] nie chroniła przed zniszczeniem wyprowadzony obiekt za pomocą takiego wskaźnika, więc po prostu zniszczenie go to UB?)