Страницы

Поиск по вопросам

воскресенье, 8 декабря 2019 г.

Что такое адаптер?

#cpp


Читая книгу по STL я столкнулся с термином адаптер и сразу полез в поисковик что
бы осветить этот вопрос и ни чего полезного не нашел.

В книге говорится что это реализация одного типа контейнера другим типом контейнера.
В общем понятного мало.

Расскажите своими словами что такое адаптер в STL.
    


Ответы

Ответ 1



Хочу начать с небольшого предисловия: такого понятия как STL в C++ не существует, есть стандартная библиотека и этого термина стоит придерживаться. Раньше термин STL использовали для обозначения стандартной библиотеки, но сейчас это считается моветоном. Что же такое адаптер? Это, по существу, устоявшийся паттерн, который приходит к нам из реального мира. Мы берём одну сущность и адаптируем её к новым условиям через другую сущность. Безусловно, понятнее не стало. Что имеется в виду в книге? Вероятно там разговор идёт о стеке и прочих контейнерах-адапторах. Почему они адапторы? Потому что внутри себя используют полноценный контейнеры, такие как std::vector и std::deque, и адаптируют их интерфейс под другой интерфейс. К примеру, std::vector имеет методы push_back, insert и pop_back, но такие операции для стека не нужны, стеку нужны 3 операции(основные) top, pop и push. Но все эти операции реализованы через соответсвующие операции std::vector, который скрывается в недрах std::stack. Поэтому stack не выделяют как самостоятельный контейнер — он есть адаптер для std::vector. Другими словами: адаптер это понятие, которое выходит далеко за рамки стандартной библиотеки C++ и, в целом, им можно пренебречь изучая C++, т.к. является ли контейнер адаптером или нет не является ключевой частью. Это интересно знать, но не более того.

Ответ 2



Адаптировать - это значит приспосабливать. То есть приспосабливать существующую сущность, контейнер, итератор и т.д., , в новом контексте или для частной задачи. Например, стандартный контейнер двусвязный список std::list имеет такие методы, как push_back, push_front, pop_back, pop_front, front, back. Используя лишь подмножество методов, предоставляемых контейнером std::list, можно смоделировать поведение такой структуры данных, как стек. В структуре данных стек данные организованы по принципу: последний вошел в стек - первый вышел из стека, то есть LIFO (last in, first out). Обычно методы контейнера стек получают названия push, pop, top, как, например, в C++, или Push, Pop, Peek, как, например, в C# или другие названия. Но независимо от присвоенных названий методов их поведение семантически одно и то же. Чтобы построить в C++ из списка стек, достаточно использовать методы либо push_front, pop_front, front, либо push_back, pop_back, back соответственно для методов стека push, pop, top. Для этого просто в реализации данных методов стека следует вызывать соответствующие методы контейнера std::list. Например, метод стека pop может быть реализован как вызов соответствующего метода контейнера std::list, который в примерах обозначен именем c от английского слова container: void pop() { c.pop_back(); } или void pop() { c.pop_front(); } Поэтому нет необходимости определять управление данными контейнера стек. Всю работу по управлению данными возьмет на себя контейнер std::list. Просто его методы, фактически, переименованы для адаптации к названиям методов контейнера стек. Поэтому стек реализован не с нуля, а он просто является адаптацией класса std::list под логику работы стека, то есть стек - это адаптер контейнера. Чтобы было возможно как можно больше последовательных контейнеров адаптировать под стек, было принято соглашение ограничиться методами последовательных контейнеров push_back, pop_back, и back, так как эти методы более широко распространены среди последовательных контейнеров. Например, контейнер std::vector не имеет методов push_front и pop_front, поэтому его нельзя было использовать для моделирования методов стека, используя именно эти методы последовательных контейнеров. Однако стандартный последовательный контейнер std::forward_list, который был введен в стандарт C++ позже остальных стандартных последовательных контейнеров, не имеет методов push_back, pop_back и back. Поэтому адаптер контейнеров std::stack не может адаптировать этот контейнер для реализации стека. Тем не менее я уже предложил Комитетам по стандартизации C++ включить специализацию класса std::stack для контейнера std::forward_list. Это не представляет принципиальных трудностей сделать. Существует лишь одно несоответствии: класс std::forward_list не имеет метода size, который присутствует в адаптере контейнеров std::stack. С моей точки зрения это не принципиально. Аналогичный подход используется для адаптеров итераторов. Примером такого адаптера итераторов является std::reverse_iterator. Он адаптирует оператор operator -- других итераторов таким образом, что когда используется оператор operator ++, то на самом деле вызывается оператор operator --. Это позволяет перебирать элементы контейнера или последовательности в обратном порядке, не меняя код, который основывается на применении оператора operator ++ . Например, если у вас имеется функция, которая выводит в поток элементы некоторого контейнера посредством передачи в функцию диапазона элементов с помощью двух итератор, как, например template std::ostream & display( ForwardIterator first, ForwardIterator last, std::ostream &os = std::cout ) { for ( ; first != last; ++first ) { os << *first << ' '; } return os; } То, передавая реверсивные итераторы, вы могли бы, например, выводить элементы массива целых чисел в обратном порядке: #include #include template std::ostream & display( ForwardIterator first, ForwardIterator last, std::ostream &os = std::cout ) { for ( ; first != last; ++first ) { os << *first << ' '; } return os; } int main() { const size_t N = 10; int a[N] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; display( a, a + N ) << std::endl; display( std::reverse_iterator( a + N ), std::reverse_iterator( a ) ) << std::endl; } Вывод программы на консоль: 0 1 2 3 4 5 6 7 8 9 9 8 7 6 5 4 3 2 1 0 В данной программе адаптер итераторов std::reverse_iterator адаптирует указатели типа int * для операций с ними в обратном порядке следования элементов массива. Он использует те методы указателей, которые уже определены и существуют для указателей. Для разнообразия в качестве еще одного примера адаптера итераторов можете познакомиться с предложенным мной адаптером итераторов iterator_pair, который я описал в своей статье в журнале ACCU Overload #126 либо по ссылкам, приведенной на моей персональной странице здесь и здесь

Комментариев нет:

Отправить комментарий