Menu
Coddy logo textTech

Правило трех / пяти / нуля

Часть раздела Объектно-ориентированное программирование путешествия по C++ на Coddy. Урок 25 из 104.

Если ваш class управляет такими ресурсами, как динамическая память, вы уже узнали, что вам нужны пользовательские destructor, constructor копирования и конструктор перемещения. Но существует руководящий принцип, который помогает решить, какие специальные функции-члены реализовать: Правило трёх, пяти и нуля.

Правило трёх гласит: если вы define любой из этих трёх элементов, следует define все три:

  • Destructor
  • Copy constructor
  • Copy assignment operator

Rule of Five расширяет это для современного C++, добавляя операции перемещения:

  • Destructor
  • Copy constructor
  • Copy assignment operator
  • Move constructor
  • Move assignment operator
class Buffer {
    int* data;
    size_t size;
public:
    Buffer(size_t s) : size(s), data(new int[s]) {}
    
    ~Buffer() { delete[] data; }                          // 1. Деструктор
    Buffer(const Buffer& other);                          // 2. Конструктор копирования
    Buffer& operator=(const Buffer& other);               // 3. Копирующее присваивание
    Buffer(Buffer&& other) noexcept;                      // 4. Конструктор перемещения
    Buffer& operator=(Buffer&& other) noexcept;           // 5. Перемещающее присваивание
};

Rule of Zero — это самый простой подход: если ваш class напрямую не управляет ресурсами, не определяйте ни одну из этих functions. Позвольте компилятору сгенерировать их или используйте умные указатели и стандартные контейнеры, которые handles ресурсы за вас.

class Player {
    std::string name;              // std::string управляет своей собственной памятью
    std::vector<int> scores;       // std::vector управляет своими ресурсами
public:
    Player(std::string n) : name(n) {}
    // Деструктор, функции копирования или перемещения не нужны!
};

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

challenge icon

Задание

Легко

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

Вы создадите два файла, чтобы организовать свой код:

  • TextBuffer.h: Define класс TextBuffer, который хранит текст в динамически выделяемом символьном массиве. Вашему классу требуются:
    • Закрытые члены: указатель char* с именем data для содержимого текста и size_t length для длины строки (без завершающего нулевого символа)
    • Параметризованный конструктор, принимающий C-строку (const char*), выделяющий память, копирующий content и выводящий "TextBuffer created: <text>"
    • деструктор, освобождающий память (если она не равна null) и выводящий "TextBuffer destroyed"
    • конструктор копирования, выполняющий deep-копирование и выводящий "TextBuffer copied"
    • оператор присваивания копированием, обрабатывающий присваивание самому себе, очищающий existing data, выполняющий deep-копирование и выводящий "TextBuffer copy-assigned". Верните *this
    • конструктор перемещения (помеченный noexcept), передающий владение и выводящий "TextBuffer moved". Оставьте исходный объект в допустимом пустом состоянии
    • оператор присваивания перемещением (помеченный noexcept), обрабатывающий присваивание самому себе, очищающий existing data, передающий владение и выводящий "TextBuffer move-assigned". Верните *this
    • Метод getText(), возвращающий сохранённый текст (возвращающий пустую строку "", если data имеет значение null)
    • Метод getLength(), возвращающий длину
  • main.cpp: Продемонстрируйте работу всех пяти специальных функций-членов. Считайте текстовую строку из input, затем:
    • Create TextBuffer с именем original, используя введённый текст
    • Create copied с помощью конструктора копирования из original
    • Create another с текстом "Temporary"
    • Используйте присваивание копированием: another = original
    • Create moved с помощью перемещения-конструирования из original, используя std::move()
    • Create target с текстом "Target"
    • Используйте присваивание перемещением: target = std::move(copied)
    • Выведите "--- Final State ---"
    • Выведите "original: <text> (length: <len>)" для каждого буфера: original, copied, moved, another, target

После перемещений исходные объекты (original и copied) должны содержать пустой текст с длиной 0, а объекты назначения — переданные данные. Это демонстрирует Правило пяти в действии: все пять функций работают вместе, обеспечивая безопасное управление ресурсами.

Подключите <cstring> для строковых функций, таких как strlen и strcpy, и <utility> для std::move().

Попробуйте сами

#include <iostream>
#include <string>
#include <utility>
#include "TextBuffer.h"

using namespace std;

int main() {
    string input;
    getline(cin, input);

    // TODO: Создайте TextBuffer с именем 'original' с входным текстом

    // TODO: Создайте 'copied', используя конструктор копирования из 'original'

    // TODO: Создайте 'another' с текстом "Temporary"

    // TODO: Используйте присваивание копированием: another = original

    // TODO: Создайте 'moved' с помощью конструктора перемещения из 'original', используя std::move()

    // TODO: Создайте 'target' с текстом "Target"

    // TODO: Используйте присваивание перемещением: target = std::move(copied)

    // TODO: Выведите "--- Final State ---"

    // TODO: Выведите состояние каждого буфера в следующем формате:
    // "original: <text> (length: <len>)"
    // Выведите для: original, copied, moved, another, target

    return 0;
}
quiz iconПроверьте себя

В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.

Все уроки раздела Объектно-ориентированное программирование

Потренируйтесь самостоятельно: Онлайн-компилятор C++