Tuesday, November 19, 2019

Владение ресурсами в си++

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

С++11 ввел семантику перемещения, которая при наличии rvalue значения, позволяет его перемещать явно или не явно. Не явно это происходит, при оптимизации во время компиляции. Явно перемещение можно задать с помощью std::move(obj), для объектов которые поддерживают перемещение в своих типах. Поддержку перемещения можно обеспечить, явно определив конструктор перемещения и перемещающий оператор присваивания. Таким образом правило трех превратилось в правило пяти. Если мы хотим запретить копирование, то можем указать запрет на автоматическое создание с помощью =delete для конструктора копирования и копирующего оператора присваивания.
 
#include <iostream>

class Box {
public:
    //constructor
    explicit Box(int _number) : number{_number} {
        bigData = new int(number);
    }

    //destructor
    ~Box() {
        delete bigData;
    }

    //copy constructor
    Box(const Box &other) {
        number = other.number;
        bigData = new int(0);
        *bigData = *other.bigData;
    }

    //copy assign operator
    Box &operator=(const Box &other) {
        if (this == &other)
            return *this;
        number = other.number;
        delete bigData;
        bigData = new int(0);
        *bigData = *other.bigData;
        return *this;
    }

    //move constructor
    Box(Box &&other) noexcept {
        number = other.number;
        bigData = other.bigData;
        other.number = 0;
        other.bigData = nullptr;
    }

    //move assign operator
    Box &operator=(Box &&other) noexcept {
        if (this == &other)
            return *this;
        number = other.number;
        delete bigData;
        bigData = other.bigData;
        other.number = 0;
        other.bigData = nullptr;
        return *this;
    }

public:
    int *bigData = nullptr;
    int number;
};

Стандартная библиотека си++ предлагает специальные типы для решения задачи владения, так называемые умные (smart) указатели. Рассмотрим основные из них, это уникальный указатель std::unique_ptr<T> и указатель совместного владения std::shared_ptr<T>. При их использовании нет необходимости в new и delete. Также умные указатели возвращают bool при проверках. В отличие от сырых (raw) указателей, умные указатели отвечают за очистку памяти при их удалении.

int main() {
    Box box{10}; // constructor
    Box box1{box};  // copy constructor
    Box box2 = box1; // copy assign operator
    Box box3{std::move(box2)}; // move constructor
    Box box4 = std::move(box3); // move assign operator

    std::cout << (box3.bigData ? "filled" : "empty") << std::endl;
    std::cout << (box3.number ? "10" : "0") << std::endl;
    std::cout << (box4.bigData ? "filled" : "empty") << std::endl;
    std::cout << (box4.number ? "10" : "0") << std::endl;

    std::unique_ptr<Box> uPtr1 = std::make_unique<Box>(std::move(box4));
    std::cout << (uPtr1 ? "true" : "false") << std::endl;
    std::unique_ptr<Box> uPtr2 = std::move(uPtr1);
    std::cout << (uPtr1 ? "true" : "false") << std::endl;

    std::shared_ptr<Box> sPtr1 = std::make_shared<Box>(55);
    std::shared_ptr<Box> sPtr2 = sPtr1;
    std::cout << *sPtr1->bigData << std::endl;
    std::cout << *sPtr2->bigData << std::endl;

    return 0;
}

Output:
>>empty
>>0
>>filled
>>10
>>true
>>false
>>55
>>55

Перегуд В.

No comments: