Навчальний простір / C++
Об’єктно-орієнтоване програмування

Поліморфна колекція та RAII-власники

Поліморфна колекція поєднує спільний інтерфейс та різні реалізації.

Зміст матеріалу

Мета й середовище

Створити колекцію різних реалізацій спільного інтерфейсу та пояснити володіння кожним об’єктом. Потрібно знати virtual, override, деструктор, vector і unique_ptr. Використайте C++20 compiler, створіть polymorphic-lab/main.cpp. Зберіть із -Wall -Wextra -pedantic; для підтримуваних Clang/GCC додатково перевірте -fsanitize=address,undefined -g.

Теоретичний мінімум

Динамічний поліморфізм обирає реалізацію virtual функції за фактичним типом об’єкта. Unique_ptr забезпечує одного власника й автоматичне знищення. Vector керує самими власниками. Віртуальний деструктор бази забезпечує коректне видалення похідного через базовий тип. Переміщення власника не копіює керований ресурс.

Демонстрація обчислює площі навчальних фігур. Базовий контракт повертає скінченну невід’ємну площу для додатних розмірів. Одиниці довжини погоджені, тому результат має квадратні одиниці. У конструкторі перевіримо також скінченність, щоб infinity і NaN не пройшли як звичайні додатні значення.

Повний код демонстрації

main.cpp cpp
#include <cassert>
#include <cmath>
#include <iostream>
#include <memory>
#include <stdexcept>
#include <vector>

class Shape {
public:
    virtual ~Shape() = default;
    virtual double area() const = 0;
};
class Rectangle final : public Shape {
    double width_, height_;
public:
    Rectangle(double w, double h) : width_(w), height_(h) {
        if (!std::isfinite(w) || !std::isfinite(h) || w <= 0 || h <= 0)
            throw std::invalid_argument("dimensions");
        if (!std::isfinite(w * h)) throw std::invalid_argument("area overflow");
    }
    double area() const override { return width_ * height_; }
};
class Square final : public Shape {
    double side_;
public:
    explicit Square(double side) : side_(side) {
        if (!std::isfinite(side) || side <= 0 || !std::isfinite(side * side))
            throw std::invalid_argument("side");
    }
    double area() const override { return side_ * side_; }
};
int main() {
    std::vector<std::unique_ptr<Shape>> shapes;
    shapes.push_back(std::make_unique<Rectangle>(3, 4));
    shapes.push_back(std::make_unique<Square>(2));
    double total = 0;
    for (const auto& shape : shapes) total += shape->area();
    assert(std::abs(total - 16) < 1e-9);
    std::cout << "total=" << total << '\n';
}

Очікується total=16. Rectangle дає 12, Square — 4. Кожен unique_ptr володіє конкретним похідним об’єктом; base інтерфейс не розкриває його поля. Цикл позичає const доступ до власників і не копіює їх. При виході з main vector знищує елементи, а кожен власник — свій Shape через virtual деструктор.

Покрокове виконання

Спершу намалюйте карту: vector → unique_ptr → конкретна фігура. Поясніть, які об’єкти мають автоматичний час життя, а які створені динамічно. Зберіть і запустіть демонстрацію. Додайте тест нульового розміру й перевірте invalid_argument. Перевірте порожню колекцію: сумарна площа повинна дорівнювати нулю.

Створіть unique_ptr у локальній змінній, перемістіть його в vector через push_back(std::move(owner)) і перевірте, що owner порожній. Не розіменовуйте його після передачі. Збережіть незалежний очікуваний результат площі. Якщо експериментуєте з журналюванням деструкторів, не покладайтеся на порядок знищення елементів vector як на бізнес-правило.

Самостійне завдання

Створіть інтерфейс навчального сповіщення з двома різними реалізаціями форматування. Задайте спільний контракт: передані дані не зберігаються позичено після виклику, результат є незалежним рядком, помилкові дані мають погоджену реакцію. Створіть vector власників і обробіть набір повідомлень одним циклом.

Виберіть власні формати й приклади. Напишіть перевірки кожної реалізації та спільного контракту. Не додавайте реальне надсилання email або мережеві секрети: робота перевіряє форматування й поліморфне володіння. Додатковий варіант — передати готовий набір у функцію читання без копіювання власників, пояснивши тип параметра.

Типові помилки та налагодження

Відсутній virtual деструктор робить видалення через базу некоректним. Vector базових значень може спричинити slicing або бути неможливим для абстрактного класу. Копіювання unique_ptr не підтримується. Збереження string_view на тимчасовий рядок створює висячий доступ. Для кожного збою назвіть власника, тип операції та момент життя цілі.

Результат і контрольні питання

Подайте код, карту володіння, команди збірки та результати контрактних тестів. Поясніть, хто видаляє об’єкти й чому ручний delete не потрібний. Що робить std::move? Чому const посилання на власника не передає володіння? Який тест підтверджує незалежність повернутого рядка від вхідних даних?

Поліморфізм, власники та перевірка життя

Простежте створення кожної фігури через make_unique та передавання власника у vector. Після цього vector відповідає за завершення життя об’єктів. Локальний покажчик, отриманий через get, є позиченим; він не повинен пережити власника або використовуватися після видалення відповідного елемента.

Для геометрії перевірте додатні скінченні розміри, нуль, від’ємне число та настільки великий розмір, що площа виходить за допустиме представлення. Isfinite допомагає відхилити NaN та infinity. Порівняння лише width > 0 не перевіряє всіх наслідків множення. Збережіть реакцію функції на кожен випадок.

В основному завданні з форматування повідомлень визначте спільний зміст результату й вимоги до порожнього входу. Обидві реалізації повинні підтримувати базовий контракт, щоб один цикл міг обробляти їх без спеціальних умов за типом. Не додавайте перевірку назв похідних класів для вибору поведінки, якщо її вже задає virtual-метод.

Поясніть, чому деструктор бази віртуальний і як це пов’язано зі знищенням через unique_ptr базового типу. Для налагодження можна додати тимчасове повідомлення в деструкторах і спостерігати порядок; після досліду збережіть чистий варіант рішення. У звіті наведіть фактичний вивід, контракт кожної реалізації та схему власності без ручного delete.

Окремий контрольний сценарій

Для сумарної площі перевірте порожню колекцію та одну фігуру. Порожня сума дорівнює нулю за контрактом прикладу, а один елемент дозволяє звірити virtual-виклик із прямим очікуванням. У власному форматуванні визначте, чи порожній список означає порожній текст або окреме повідомлення. Перед демонстрацією додайте ці випадки в таблицю тестів і поясніть, як контракт базового типу підтримує однаковий цикл обробки.

Схема процесу

Поліморфна колекція та RAII-власникиОберіть елемент, щоб побачити пояснення

Задаємо поведінку базового типу.

Самоперевірка

Перевірте розуміння
Хто звільняє похідні об’єкти у демонстрації?
Як обґрунтувати відповідь своїми словами?
RAII-власники звільняють керовані об’єкти. Віртуальний деструктор бази забезпечує правильне похідне знищення.
Перед завершенням роботи

Підсумок

Поліморфна колекція поєднує спільний інтерфейс та різні реалізації. Unique_ptr робить володіння явним, а virtual деструктор підтримує коректне знищення. Самостійне форматування перевіряє цей механізм без зовнішніх залежностей.

Джерела

Як оформити та здати роботу →

Опрацювали матеріал?

Збережіть цей крок у своєму прогресі.

← Повернутися до дисципліни
© 2026 Анастасія Іскандарова-МалаЕлектронний навчальний посібник · ДДТУ

Пошук у посібнику

Спробуйте «C++», «RAII» або «бази даних».