Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
ООП_ Лекция №10 - Более сложные формы композиции.docx
Скачиваний:
0
Добавлен:
01.07.2025
Размер:
68.53 Кб
Скачать

Использование средства std::function

Проблему инкапсуляции конкретной структуры данных, используемой для хранения дочерних объектов в классе-родителе, можно решить несколько другим способом. Альтернативное решение состоит в отказе от идеи предоставления клиентскому коду итераторов на внутренние контейнеры в пользу передачи родителю интересующего действия. Действиет может быть задано в виде любой вызываемой сущности - функции, функционального объекта и лямбда-выражения.

Для решения этой задачи можно воспользоваться универсальной вызываемой сущностью std::function. Допустим, необходима вызываемая сущность, которая ничего не возвращает и принимает константную ссылку на объект Chapter. Объявление типа для такого объекта выглядит следующим образом:

std::function< void ( Chapter const & ) > f;

Такой объект может быть инициализирован любым из трех видов вызываемых сущностей:

// Обыкновенная функция

void printChapter ( Chapter const & _chapter )

{

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

}

// Инициализируем std::function обыкновенной функцией

std::function< void ( Chapter const & ) > f1 = & printChapter;

// Функциональный объект (функтор)

struct ChapterPrinter

{

void operator () ( Chapter const & _chapter )

{

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

}

};

// Инициализируем std::function функциональным объектом

std::function< void ( Chapter const & ) > f2 = ChapterPrinter();

// Инициализируем std::function лямбда-выражением

std::function< void ( Chapter const & ) > f3 =

[] ( Chapter const & _chapter )

{

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

};

Наличие такого мощного средства позволяет совершить радикальные изменения при реализации итерирования дочерних объектов в родительском классе. Суть предлагаемого подхода состоит в полном отказе от предоставления итераторов и методов, которые их возвращают - их можно удалить. Итераторы заменяются специальным методом для обхода дочерних объектов, принимающим желаемое действие на каждом из элементов в виде объекта std::function. Ниже представлена очередная версия класса Book с применением предложенного варианта:

class Book

{

public:

// Функция обхода глав - принимает действие, которое применяется ко всем главам

void forEachChapter ( std::function< void ( Chapter const & ) > _action ) const

{

// Обходим контейнер глав и вызываем переданное действие на каждой из глав.

// Каждый элемент chapterPtr - это std::unique_ptr< Chapter > const &

for ( auto const & chapterPtr : m_chapters )

_action( * chapterPtr );

// ^

// клиент получит ссылку на главу

}

// ...

};

Тестовый код видоизменяется следующим образом - в качестве действия для элементов передается лямбда выражение, принимающее ссылку на главу:

pBook->forEachChapter(

[] ( Chapter const & _chapter )

{

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

}

);

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