Как освободить память с помощью конструктора перемещения

#c

#c

Вопрос:

Я пытаюсь создать свой собственный вектор, и вот минимальный пример, чтобы представить проблему, с которой я столкнулся:

 class DemoVector {
public:
    DemoVector() : capacity_(1), size_(0) {
        data_ = new int[1];
    }

    DemoVector(DemoVectoramp;amp; rhs) {
        data_ = std::move(rhs.data_);
        size_ = rhs.size_;
        capacity_ = rhs.capacity_;
    }

    ~DemoVector() {
        delete[] data_;
    }

    void PushBack(const int amp;v) {
        // doesn't matter
    }

private:
    int *data_;
    size_t capacity_;
    size_t size_;
};
  

Тест:

 TEST_CASE("Test") {
    DemoVector b;
    b.PushBack(1);
    DemoVector c(std::move(b));
}
  

У меня здесь проблема, и я понимаю, почему. У меня есть два объекта, которые указывают на одну и ту же память. Второй деструктор пытается освободить память, которая уже была освобождена первым деструктором.

Но я не знаю, как это исправить. Спасибо за вашу помощь.

Комментарии:

1. Существует причина, по которой перемещаемый объект отсутствует const в конструкторе перемещения.

Ответ №1:

std::move(rhs.data_) на самом деле ничего не перемещает. std::move это не что иное, как именованное приведение. Он создает ссылку на значение rvalue, которая позволяет выполнять семантику перемещения. Но для примитивных типов это просто операция копирования. Указатель копируется, и в итоге вы получаете два указателя, которые содержат один и тот же адрес. Поскольку вы не хотите, чтобы исходный объект по-прежнему указывал на тот же буфер, просто измените его. Вот почему семантика перемещения строится вокруг неконстантных ссылок.

Конструкторы перемещения сейчас стали обычным явлением, поэтому есть стандартная утилита (C 14), которая помогает писать их таким образом, чтобы код вел себя более точно, как вы ожидаете. Это std::exchange . Вы можете просто написать

 DemoVector(DemoVectoramp;amp; rhs)
  : data_(std::exchange(rhs.data_, nullptr))
  , size_(std::exchange(rhs.size_ , 0))
  , capacity_(std::exchange(rhs.capacity_ , 0))
{}
  

И все значения корректируются правильно. std::exchange изменяет свой первый аргумент, чтобы сохранить значение второго аргумента. И, наконец, он возвращает старое значение первого аргумента. Очень удобно перемещать значения в однострочных инициализациях.

Ответ №2:

Потому std::move что это в основном просто приведение, которое на самом деле ничего не перемещает! Вам необходимо самостоятельно обновить значения в другом объекте:

 DemoVector(DemoVectoramp;amp; rhs) {
    data_ = rhs.data_;
    size_ = rhs.size_;
    capacity_ = rhs.capacity_;
    rhs.data_ = nullptr;
    rhs.size_ = 0;
    rhs.capacity = 0;
}
  

Или, в качестве альтернативы, использовать существующий конструктор:

 DemoVector(DemoVectoramp;amp; rhs): DemoVector() {
    // Or write your own swap function to reuse this elsewhere
    std::swap(data_, rhs.data_);
    std::swap(size_, rhs.size_);
    std::swap(capacity_, rhs.capacity_);
}
  

Вам решать, как вы хотите, чтобы пользователи вашего класса обрабатывали перемещенные объекты. Во втором случае, а возможно, и в первом, в зависимости от того, как работает остальная часть вашего класса, rhs ( b в вашем тестовом примере) будет пустой вектор.

Комментарии:

1. Обратите внимание на выбор между реализациями: замена происходит незначительно медленнее, чем присвоение null , но это весьма полезно для установления инварианта класса data_ , который никогда не будет null, что может позволить удалять проверки null во многих местах.