Почему нет перемещения объектов в векторе, когда зарезервирована дополнительная память?

#c #stdvector #move-semantics

#c #stdvector #перемещение-семантика

Вопрос:

Существует следующий код:

 #include <iostream>
#include <vector>

class Test {
   public:
    Test() {}
    Test(int x) : x(x) {}

    Test(const Testamp;) noexcept = delete;
    Testamp; operator=(const Testamp;) noexcept = delete;

    Test(Testamp;amp; r) noexcept { x = std::move(r.x); }
    Testamp; operator=(Testamp;amp; r) noexcept {
        std::cout << "move";
        x = std::move(r.x);

        return *this;
    }

    ~Test() noexcept { std::cout << x; }

   private:
    int x;
};

int main() {
    std::vector<Test> v;

    v.reserve(5);
    v.emplace_back(1);
    v.emplace_back(2);
    v.emplace_back(3);
    v.emplace_back(4);
    v.emplace_back(5);
    v.reserve(10);
}
 

вывод:

 1234512345
 

Мы видим деструкторы, но не видим назначение перемещения, поэтому мы можем сделать вывод, что объект копируется, а не перемещается.
Этот вопрос уже обсуждался в нескольких темах, но нигде нет конкретного ответа.

Существует обходной путь с использованием unique_ptrs:

 #include <iostream>
#include <vector>
#include <memory>

class Test {
   public:
    Test() {}
    Test(int x) : x(x) {}

    Test(const Testamp;) noexcept = delete;
    Testamp; operator=(const Testamp;) noexcept = delete;

    Test(Testamp;amp; r) noexcept { x = std::move(r.x); }
    Testamp; operator=(Testamp;amp; r) noexcept {
        std::cout << "move";
        x = std::move(r.x);

        return *this;
    }

    ~Test() noexcept { std::cout << x; }

   private:
    int x;
};

int main() {
    std::vector<std::unique_ptr<Test>> v;

    v.reserve(5);
    v.emplace_back(std::make_unique<Test>(1));
    v.emplace_back(std::make_unique<Test>(2));
    v.emplace_back(std::make_unique<Test>(3));
    v.emplace_back(std::make_unique<Test>(4));
    v.emplace_back(std::make_unique<Test>(5));
    v.reserve(10);
}
 

вывод:

 12345
 

Весь код протестирован на компиляторах gcc 5.1 — 10.2

Почему копирование объектов происходит при выделении памяти в векторе вместо перемещений? Почему семантика перемещения не используется в первом блоке кода, в чем причина такого поведения?

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

1. неясно, в чем «проблема» или из чего вы делаете вывод. Пожалуйста, включите выходные данные и объясните, что еще вы ожидали

2. «согласно выводам деструктора, мы можем сделать вывод, что объект копируется» — что это значит? Даже перемещенный из объекта должен вызывать деструктор, поэтому действительно неясно, что вы наблюдаете

3. Мне непонятно, как вы пришли к выводу «Где, согласно выводам деструктора, мы можем сделать вывод, что объект копируется»..

4. Я действительно не знаю, полезно ли это, но перемещение int — это то же самое, что его копирование. Вы ожидали, что исходное значение будет равно 0?

5. @rededx Длительность статического хранения имеет очень специфическое значение в C , и оно здесь не применяется. Я не совсем уверен, о чем спрашивает ваш комментарий.

Ответ №1:

Оператор v.reserve(10); заставляет ваш вектор перераспределять свое хранилище, чтобы оно могло вместить не менее 10 элементов. Предполагая capacity , что меньше 10, это четырехэтапный процесс :

  1. Выделяется новое хранилище, достаточно, чтобы capacity их было не менее 10.
  2. Элементы из предыдущего хранилища необходимо переместить в новое хранилище. Для каждого элемента в предыдущем хранилище переместите значение этого элемента в новый экземпляр в новом хранилище.
  3. Прежде чем можно будет освободить старое хранилище, перемещенные экземпляры все еще необходимо уничтожить. Это приводит к выполнению каждого их деструктора, который печатается 12345 в вашем случае. Обратите внимание, что перемещенные экземпляры по-прежнему имеют свое старое значение x . Перемещение int эквивалентно его копированию, исходное значение остается неизменным.
  4. Предыдущее хранилище теперь освобождено. Теперь вектор использует новое хранилище, которое содержит эквивалентные элементы, но больше, чем было предыдущее хранилище.

Наконец, в конце main ваш вектор v должен быть уничтожен. Это также приводит к уничтожению каждого из его элементов, что приводит к выполнению каждого из деструкторов. Это второй набор 12345 , который печатается.

При std::unique_ptr этом вы видите только один набор чисел ( 12345 ), потому unique_ptr что s перемещаются между двумя хранилищами, а не Test s. Новые перемещенные экземпляры std::unique_ptr<Test> приобретают право собственности на указатель, а старые перемещенные экземпляры больше не владеют этими указателями. Когда экземпляры из предыдущего хранилища уничтожаются, их деструкторы ничего не делают (потому что они больше не владеют указателем). Вы видите уничтожение ваших Test экземпляров только тогда, когда v уничтожается в конце main . Затем std::unique_ptr уничтожаемые экземпляры имеют собственные указатели Test , поэтому в результате Test экземпляры становятся delete общими.

Редактировать: Обратите внимание, что вы только инструментировали свой оператор присваивания перемещения. std::vector в этом случае будет использоваться конструктор перемещения, поэтому вы не увидите "move" , даже когда экземпляры Test перемещаются v .

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

1. Большое вам спасибо, я буквально только что пришел к тому же выводу. В старых вопросах не было полного объяснения, я надеюсь, что кто-то еще, кроме меня, найдет это полезным.

Ответ №2:

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

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

1. std::vector требуется использовать распределитель для управления памятью, а распределители не имеют никакого realloc API, поэтому это невозможно.