У меня есть класс, который представляет список людей.
class AddressBook
{
public:
AddressBook();
private:
std::vector<People> people;
}
Я хочу позволить клиентам перебирать вектор людей. Первая мысль у меня была просто:
std::vector<People> & getPeople { return people; }
Однако я не хочу передавать детали реализации клиенту . Я могу захотеть сохранить определенные инварианты при изменении вектора, и я теряю контроль над этими инвариантами, когда пропускаю реализацию.
Какой лучший способ разрешить итерацию без утечки внутренних данных?
begin()
иend()
опасно, потому что (1) эти типы являются векторными итераторами (классами), которые не позволяют одному переключиться на другой контейнер, такой какset
. (2) Если вектор был изменен (например, вырос или некоторые элементы удалены), некоторые или все итераторы вектора могли быть признаны недействительными.Ответы:
разрешить итерацию без утечки внутренних данных - это именно то, что обещает шаблон итератора. Конечно, это в основном теория, поэтому вот практический пример:
Вы предоставляете стандарт
begin
иend
методы, как последовательности в STL, и реализуете их, просто перенаправляя в метод вектора. Это приводит к утечке некоторых деталей реализации, а именно, что вы возвращаете векторный итератор, но ни один здравомыслящий клиент никогда не должен зависеть от этого, так что это не является проблемой. Я показал все перегрузки здесь, но, конечно, вы можете начать с предоставления константной версии, если клиенты не смогут изменять какие-либо записи сотрудников. Использование стандартного именования имеет свои преимущества: любой, кто читает код, сразу же знает, что он обеспечивает «стандартную» итерацию и, как таковой, работает со всеми распространенными алгоритмами, диапазоном на основе циклов и т. Д.источник
begin()
иend()
просто направляет векторbegin()
иend()
позволяет пользователю изменять элементы в самом векторе, возможно, используяstd::sort()
. В зависимости от того, какие инварианты вы пытаетесь сохранить, это может быть или не быть приемлемым. Предоставлениеbegin()
иend()
, тем не менее, необходимо для поддержки циклов C ++ 11 на основе диапазона.Если итерация - это все, что вам нужно, то, возможно,
std::for_each
будет достаточно обертки вокруг :источник
const
итерации.for_each()
Являетсяconst
функцией - членом. Следовательно, членpeople
рассматривается какconst
. Следовательноbegin()
иend()
перегрузит какconst
. Следовательно, они вернутсяconst_iterator
кpeople
. Следовательно,f()
получитPeople const&
. Написаниеcbegin()
/cend()
здесь ничего не изменит на практике, хотя, как одержимый пользователь,const
я могу утверждать, что это все еще стоит делать, так как (а) почему бы и нет; это всего лишь 2 символа, (б) мне нравится говорить, что я имею в виду, по крайней мере, сconst
, (в) это защищает от случайного вставления где-то не-const
и т. д.Вы можете использовать идиому pimpl и предоставить методы для перебора контейнера.
В шапке:
В источнике:
Таким образом, если ваш клиент использует typedef из заголовка, он не заметит, какой тип контейнера вы используете. И детали реализации полностью скрыты.
источник
Можно обеспечить функции-члены:
Которые разрешают доступ без раскрытия деталей реализации (например, смежности) и используют их в классе итератора:
Затем итераторы могут быть возвращены адресной книгой следующим образом:
Возможно, вам понадобится дополнить класс итератора чертами и т. Д., Но я думаю, что это сделает то, что вы просили.
источник
если вы хотите точную реализацию функций из std :: vector, используйте частное наследование, как показано ниже, и управляйте тем, что выставлено.
Изменить: это не рекомендуется, если вы также хотите скрыть внутреннюю структуру данных, т.е. std :: vector
источник
vector
ними, который вы никогда не хотите использовать, но тем не менее должны наследовать?) и, возможно, активно опасны (что, если класс, от которого лениво наследуется, может быть удален через указатель на этот базовый тип где-то, но это [безответственно] не защитит от уничтожения производный объект через такой указатель, так что просто уничтожить его - UB?)