зачет 26 мая в 17-30

"Чистый" зачёт - 

Контрольное занятие. Zoo++

Задание
 состоит из четырёх частей. Все задания выполняются строго 
последовательно. Работать надо быстро. Но качественно.

Часть 1. Животные - 15 баллов

  1. В заголовочном файле Animals.h создайте класс Animal с двумя чисто виртуальными функциями-членами: say (константная) и move (неконстантная); в классах-наследниках они будут возвращать строки-описания того, какие звуки издают животные и как они передвигаются. Там же создайте три класса-открытых наследника Animal: Mammal, Bird, Reptile (млекопитающие, птицы, пресмыкающиеся), с пустым телом. Там же создайте открытых наследников классов Mammal, Bird и Reptile — конкретных видов животных: двух млекопитающих, одну птицу, одно пресмыкающееся, в этих классах должны быть определены функции-члены say и move. Конкретные виды животных и особенности их жизнедеятельности можно смотреть в Википедии.

  2. В деструкторе Animal выводите на консоль: « — животное погибло». Сообщение перед дефисом должно выводиться в деструкторах производных классов («Коровы больше нет» и т. п.).

    Для симметрии реализуйте в конструкторе того же животного вывод сообщения о том, что условная корова появилась на свет. Как изменилось поведение основной программы?

  3. Для заселения животных в будущий зоопарк надо научиться их клонировать: добавьте в класс Animal чисто виртуальную константную функцию clone, возвращающую указатель на животного-клона. В конкретных классах определите её реализацию, создающую и возвращающую указатель на копию текущего животного (this) в динамической памяти:

    return new Cow(*this); // клонированная корова

Часть 2. Зоопарк - 15 баллов

  1. Создайте класс зоопарка Zoo, который хранит полиморфную коллекцию зверей по указателям на базовый класс (в качестве конкретного класса-коллекции можно выбрать std::vector). Создайте в классе функцию-член addAnimal, которая добавляет некоторого животного в зоопарк (подсказка: параметр функции имеет тип Animal *). В её реализации должно выполняться клонирование и помещение адреса клона в хранящуюся в зоопарке коллекцию.

    Замечание. Подумайте, как можно было бы реализовать Zoo::addAnimal без функции-члена Animal::clone.

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

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

  4.  Создайте функцию-член Zoo::walk, в которой каждое животное в зоопарке издаёт звуки и передвигается. В её реализации рекомендуется использовать for из C++11 (aka foreach). Помните, что обращение к функциям-членам по указателям на объекты принято осуществлять с помощью операции ->. Продемонстрируйте его работу в основной программе.

Часть 3. Персональная информация о животных 15 баллов

Время добавить персональную информацию животным — имя и возраст. Вопрос, как лучше это сделать, не прост.

Вариант 1 [плохой]. Можно было бы добавить соответствующие поля и геттеры в класс Animal. Однако общая рекомендация при создании больших иерархий классов состоит в том, чтобы разделять наследование интерфейса и наследование реализации. Практическую пользу от этого мы увидим в части 4. А пока просто добавьте в «интерфейс» Animal чисто виртуальные константные функции-члены getName и getAge.

Вариант 2 [получше]. Вынести реализацию в отдельный класс, назовём его Creature, и добавить этот класс в иерархию каким-нибудь хитрым способом: чтобы каждый конкретный тип животного наследовал интерфейс от Animal, а реализацию части этого интерфейса (getName/getAge) от Creature. Чтобы в C++ такое решение корректно работало, сам Creature должен реализовывать интерфейс Animal (то есть наследоваться от Animal). То есть можно было бы составить цепочку наследования:

AnimalCreature ← [конкретное животное].

Однако в нашей цепочке есть ещё одно звено: Mammal/Bird/Reptile. Вставить это звено можно в одно из двух мест в данной цепочке:

  1. Слева от Creature — тогда получится, что Creature одновременно и млекопитающее и птица и рептилия;

  2. Справа от Creature — мы получим смешение наследования интерфейса и реализации, от которого стремились уйти: классы конкретных животных будут наследовать от одного класса сразу и интерфейс и реализацию.

Удовлетворительный выход из сложившейся ситуации (которая должна казаться безвыходной!) — превращение Creature в шаблон следующего вида, чтобы получить цепочку вида Animal???Creature<???> ← [конкретное животное], где вместо ??? означает Mammal/Bird/Reptile.

template<typename T>
class Creature : public T {
    // закрытые поля name, age 
    // открытые геттеры для полей;
    // конструктор с инициализацией полей
    // (тип T никак не используется)
};

Конкретное животное теперь должно выглядеть так:

class Cow : public Creature<Mammal> {
    // конструктор с параметрами для вызова конструктора Creature<Mammal>
};
  1.  Выполните реализацию Creature и изменение классов конкретных животных (наследование и конструкторы).

  2. Добавьте в класс Animal закрытую виртуальную функцию-член print с одним параметром — выходным потоком; реализация этой функции должна использовать getName и getAge. Определите операцию вывода в поток для класса Animal, в ней нужно вызвать функцию print. Попробуйте распечатать с помощью данной операции вывода в поток объект какого-либо животного. 

Часть 4. Производство зоопарков - 15 баллов

  1. Для массового производства зоопарков и заполнения их животными создайте два базовых класса, ZooCreator и AnimalCreator с пустыми открытыми виртуальными деструкторами. (Это можно сделать в одном заголовочном файле, например, ZooCreation.h.) В первом классе объявите чисто виртуальную функцию

    Zoo *    create()

    во втором — чисто виртуальную функцию

    Animal * create(string const & spec, string const & name, int age)

    где spec — это строковое описание вида конкретного животного.

    Замечание. Функции с именами create и clone часто используются для размещения объектов в динамической памяти. В этом случае к ним нужно добавлять комментарии, настойчиво требующие от клиента этих функций освобождения этой памяти с помощью delete. Добавьте такие комментарии там, где это необходимо.

  2.  Создайте открытого наследника AnimalCreator — класс SimpleAnimalCreator, который в основе алгоритма создания животного использует длинную цепочку if’ов по параметру spec. В основной программе попробуйте создать и использовать полиморфно его и создаваемых им животных. Не забывайте освобождать динамическую память.

  3.  Создайте открытого наследника ZooCreator — класс StreamZooCreator, заполняющий зоопарк на основе информации о животных, которая приходит из переданного потока, с помощью переданного создателя животных (AnimalCreator). У класса должно быть два поля типов istream & и AnimalCreator *, которые инициализируются в конструкторе. Функция create в цикле считывает из потока информацию, необходимую для создания животного, и вызывает функцию AnimalCreator::create. Результат последней должен передаваться в Zoo::addAnimal для создаваемого зоопарка и, далее, удаляться (delete).

    Попробуйте создать зоопарк с помощью потока. Для простоты — строкового:

    istringstream iss("cow mumka 1 cow zorka 2");
    SimpleAnimalCreator sac;
    StreamZooCreator zc(iss, &amp;sac);
    Zoo * z = zc.create();
    zoo->walk();
    delete zoo;

Дополнительные задачи**

  1. Создайте открытого наследника ZooCreator — класс RandomZooCreator, заполняющий зоопарк случайными животными со случайными данными в количестве, указанном в соответствующем поле класса (инициализируется в конструкторе). Как и прежде, используется AnimalCreator. Базу имён для генерации случайных имён загрузите при создании объекта класса из файла pet-names.txt.

  2. Реализуйте наследника AnimalCreator — класс PrototypedAnimalCreator, который имеет поле-отображение std::map<std::string, Animal *> protoanimals. (Шаблон класса map находится в заголовочном файле map.) Для каждого строкового описания животного он хранит экземпляр этого животного в динамической памяти. При вызове create по параметру spec из protoanimals достаётся соответствующее животное, ему задаются имя и возраст, и, наконец, в качестве результата возвращается его клон. На данный момент в программе нет возможности изменения имени и возраста животного. Добавьте в класс Animal закрытые сеттеры этих полей, объявите класс PrototypedAnimalCreator другом Animal. Реализация сеттеров выполняется каждом конкретном классе животного отдельно. Для этого потребуется добавить защищённые сеттеры в класс Creature. (Оцените затраты на изменение интерфейса…)

Обычное занятие

Часть 1. Разминка

  1. Скачайте заготовку и посмотрите реализацию first_find_cond и find_sum_min_in_pair_cond.
  2. Придумайте ещё по одной функции для задания условия и по одной лямбда-функции.
  3. Реализуйте шаблон функции

    bool in_range(It beg, It end, It it)

    которая проверяет, что it находится в диапазоне [beg,end) с помощью обычного цикла.

  4. [функция std::next, функция std::distance] Реализуйте шаблон функции

    It random_it(It beg, It end)

    которая возвращает случайный итератор в диапазоне [beg,end).

    Указания. Использовать rand + next. В assert для проверки использовать in_range. Вызов srand делается в основной программе.

  5. [функция std::rotate] Реализуйте шаблон функции

    void shift_right(It begin, It end, size_t k)

    которая выполняет циклический сдвиг элементов диапазона [beg,end) на k элементов вправо. Для применения rotate нужно вычислить (и получить с помощью next) какой элемент окажется на месте первого. Понадобится, как в прошлой задаче, узнать длину диапазона.

Каждый решение должно сопровождаться набором автоматических тестов в основной программе на разных контейнерах (как минимум: массив и std::list) с разными типами элементов.

Часть 2*. Анализ содержимого контейнеров

  1. [0.5 балла, шаблон accumulate (2)] Создайте шаблон функции, которая находит произведение элементов данного диапазона (диапазон, англ. range, всегда задаётся итераторами начала и конца), начиная умножение со значения init:

    T product(It beg, It end, T const & init)
  2. [1 балл, шаблон for_each] Создайте шаблон функции, которая находит среднее арифметическое элементов данного диапазона:

    double mean(It beg, It end)

    — на основе лямбда-функции с захватом по ссылке переменных для суммы и для количества элементов. Сумма имеет тип double, а счётчик (count) — тип int.

  3. [1.5 балла, шаблон min_element (2)] Создайте шаблон функции, которая в данном диапазоне находит итератор на элемент наиболее близкий к данному числу R (т. е. такой элемент X, для которого величина |X - R| является минимальной). Компаратор реализуется в виде лямбда-функции.

  4. [2 балла, шаблон adjacent_find (2)] Создайте шаблон функции, которая проверяет, что в данном диапазоне чередуются чётные и нечётные элементы.

  5. [2.5 балла, count] Создайте шаблон функции

    vector<T> count_n_populate(It beg, It end, T const & val)

    которая подсчитывает количество элементов в данном диапазоне, равных val, с помощью алгоритма count и возвращает вектор из этого числа копий элемента val с помощью конструктора вектора (2).