Итерирование дочерних элементов с учетом умных указателей 


Мы поможем в написании ваших работ!



ЗНАЕТЕ ЛИ ВЫ?

Итерирование дочерних элементов с учетом умных указателей



 

В предыдущем блоке была намеренно опущена одна созданная проблема: вместе с переходом на использование умных указателей несколько теряет аккуратность ранее созданный набор методов для итерирования глав. Ранее удавалось организовать относительно простой способ итерирования, где элементами были указатели на объекты Chapter, т.к. контейнер хранил эти указатели непосредственно:

 

typedef std::vector< Chapter * >::const_iterator ChapterIterator;

// Методы, возвращающие начальный и конечный итераторы контейнера глав

ChapterIterator chaptersBegin() const { return m_chapters.begin(); }

ChapterIterator chaptersEnd() const { return m_chapters.end(); }

 

С внесенными изменениями контейнер глав более не хранит обычных сырых указателей, а использует умные указатели std::unique_ptr< Chapter >, соответственно, тип элемента должен быть обновлен и для итератора:

 

typedef std::vector< std::unique_ptr< Chapter > >::const_iterator ChapterIterator;

 

Это несколько негативно влияет на использующий класс Book тестовый код. Умные указатели, доступ к которым предоставляет обновленный итератор, не предназначены для копирования, соответственно приходится использовать константные ссылки на умные указатели на главы:

 

int main ()

{

// Создаем тестовую книги двумя способами

Book * pBook = createTestBook();

 

// Перебираем главы первой книги через интервальный цикл for

for (std::unique_ptr< Chapter > const & pChapter: pBook->chapters())

std::cout << pChapter->getTitle() << std::endl;

 

// Уничтожаем книгу

delete pBook;

}

 

Читабельность и удобство здесь явно утеряны. Немного смягчить этот эффект может применение ключевого слова auto. При чем, его нужно применять с явным указанием, что передается именно константная ссылка на некий сложный тип, который мы просим автоматически определить. Если этого не сделать, логика компилятора по умолчанию выберет самый простой вариант - аргумент по значению, что не будет работать в нашем случае, т.к. умный указатель нельзя копировать:

 

for (auto const & pChapter: pBook->chapters())

std::cout << pChapter->getTitle() << std::endl;

 

Однако, это не решает созданную проблему по сути. В предыдущей лекции подчеркивалась важность абстрагирования открытых операций родительского класса от конкретного способа хранения дочерних элементов, что существенно упрощает написание и поддержку клиентского кода. В данном случае, рекомендации опять нарушаются, поскольку чисто технический аспект (идея хранить данные в умных указателях) влияет на способ использования родительского класса.

 

Необходимо задуматься о принципиально другом решении. Клиентский код должен всегда получать максимально простой интерфейс для перебора элементов независимо от выбираемого реализацией способа хранения. Одно из возможных решений состоит в создании собственного итератора для глав, ориентированного на простоту клиентского кода, но при этом скрывающего сложность работы с выбранной структурой данных в реализации.

 

Разработка собственных итераторов уже рассматривалась в предыдущих лекциях. Особенность данного итератора - взаимодействие с другим итератором, который предоставляет контейнер из стандартной библиотеки. По сути, такой итератор-адаптер хранит в себе настоящий итератор контейнера, перенаправляет все операции на него, а в момент доступа к элементам выполняет желаемое упрощение умного указателя к ссылке.

 

Поскольку такое поведение может быть полезным для разных типов данных, имеет смысл определить реализацию в виде шаблона. Ниже представлен код реализации:

 

unique_dereferencing_iterator.hpp

 

#ifndef _UNIQUE_DEREFERENCING_ITERATOR_HPP_

#define _UNIQUE_DEREFERENCING_ITERATOR_HPP_

 

/*****************************************************************************/

 

#include <memory>

 

/*****************************************************************************/

 

 

// Итератор-адаптер для удобства перебора дочених элементов, хранимых как unique_ptr

template <

typename _Item, // Тип элемента

template < typename T > typename _Container // Шаблон типа контейнера

>

class UniqueDereferencingIterator

{

public:

 

// Синоним типа, обозначающий сам себя

typedef UniqueDereferencingIterator< _Item, _Container > Self;

 

// Синоним типа для итератора реально используемого контейнера

typedef
typename _Container < std::unique_ptr< _Item > >::const_iterator
BaseIterator;

 

// Конструктор - принимает и запоминает итератор от контейнера

UniqueDereferencingIterator (BaseIterator _baseIt)

: m_baseIt(_baseIt) {}

 

// Оператор разыменования с целью чтения: превращаем умный указатель в ccылку

_Item & operator * () const

{

return * (* m_baseIt).get();

}

 

// Оператор префиксного инкремента: перенаправляем дочернему итератору

Self & operator ++ ()

{

++ m_baseIt;

return * this;

}

 

// Оператор сравнения на равенство: перенаправляем дочернему итератору

bool operator == (Self _it) const

{

return m_baseIt == _it.m_baseIt;

}

 

// Оператор сравнения на неравенство: перенаправляем дочернему итератору

bool operator!= (Self _it) const

{

return m_baseIt!= _it.m_baseIt;

}

 

private:

 

// Итератор, полученный от контейнера

BaseIterator m_baseIt;

};

 

Методы родительского класса, возвращающие итераторы, обновляются незначительно:

 

// Синоним типа для std::vector с аллокатором по умолчанию

template < typename T >

using Vector = std::vector< T >;

 

// Синоним типа для итератора глав

typedef UniqueDereferencingIterator< Chapter, Vector > ChapterIterator;

 

ChapterIterator chaptersBegin () const

{

return m_chapters.begin();

}

 

ChapterIterator chaptersEnd () const

{

return m_chapters.end();

}

 

С этими расширениями клиентский код выглядит как прежде:

 

// Перебираем главы первой книги через интервальный цикл for

for (Chapter const & chapter: pBook->chapters())

std::cout << chapter.getTitle() << std::endl;

 

Отдельные элементы данного решения нуждаются в небольшом пояснении.

При создании вспомогательного разыменовывающего умный указатель итератора была использована специфическая конструкция, неблагозвучно называемая шаблонным параметром шаблона (template template parameter):

 

template <

typename _Item, // Тип элемента

template < typename T > typename _Container // Шаблон типа контейнера

>

class UniqueDereferencingIterator { … };

 

Это особый вид аргументов шаблона, наряду с обыкновенными типами и константами, означающий подстановку типа, который сам является шаблоном. Далее, этот аргумент используется для инстанцирования, при этом вместо аргумента-типа T подставляется умный указатель std::unique_ptr для используемого типа данных:

 

// Синоним типа для итератора реально используемого контейнера

typedef
typename _Container< std::unique_ptr< _Item > >::const_iterator
BaseIterator;

 

При инстанцировании вспомогательного разыменовывающего итератора нельзя просто подставить тип контейнера как std::vector, хотя такое решение прямо напрашивается:

 

typedef UniqueDereferencingIterator< Chapter, std::vector > ChapterIterator;

 

Причина проблемы кроется в том, что STL-контейнер std::vector, ровно как и другие контейнеры, принимает более одного аргумента-типа. Чаще всего используется один фактический аргумент, однако среди формальных аргументов имеется второй, называемый аллокатором (allocator). Этот аргумент имеет значение по умолчанию, что и обуславивает наиболее частую краткую запись с единственным подставляемым в вектор типом.

Аллокаторы представляют собой механизм тонкой настройки над способами выделения и освобождения памяти в стандартной библиотеке. Как правило, профессиональные программисты не вмешиваются в логику распределения памяти по умолчанию, только если не имеется критических требований по экономии памяти. Большинство программ прекрасно используются довольствуясь стандартным распределителем памяти, заданным для всех контейнеров по умолчанию. Тем не менее, механизм внедрен в библиотеку, и в нашем примере его влияние не получится игнорировать.

 

Чтобы в итоге состыковать разработанный вспомогательный итератор с std::vector можно воспользоваться еще одной интересной конструкцией языка - шаблоны синонимов типа. До принятия стандарта С++11 отсутствие возможности объявить typedef-шаблон раздражало многих, однако новая конструкция позволяет решить эту наболевшую проблему.:

 

// Синоним типа для std::vector с аллокатором по умолчанию

template < typename T >

using Vector = std::vector< T >;

 

Наконец, можно воспользоваться всеми этими определениями для получения итогового итератора:

 

// Синоним типа для итератора глав

typedef UniqueDereferencingIterator< Chapter, Vector > ChapterIterator;

 



Поделиться:


Последнее изменение этой страницы: 2017-02-17; просмотров: 94; Нарушение авторского права страницы; Мы поможем в написании вашей работы!

infopedia.su Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав. Обратная связь - 3.239.13.1 (0.028 с.)