копирует / перемещает элементы std :: vector при изменении размера? - PullRequest
1 голос
/ 21 июня 2020

Я возился с движением c торсами для обучения / повторения sh упражнения, и я наткнулся на кое-что неожиданное для меня. Ниже у меня есть класс person, содержащий std::string m_name;. Я использую это как тестовый класс для копирования / перемещения c 'торсов.

Вот код для быстрой справки:

#include <iostream>
#include <vector>

class person
{
public:
    std::string m_name;
    explicit person(const std::string &name) : m_name(name)
    {
        std::cout << "created " << m_name << std::endl;     
    }
    
    ~person()
    {
        std::cout << "destroyed " << m_name << std::endl;       
    }   
    
    person(const person &other) : m_name(other.m_name)
    {
        m_name += ".copied";
        std::cout << "copied " << other.m_name << " -> " << m_name << std::endl;
    }
    
    person(const person &&other) noexcept : m_name(std::move(other.m_name))
    {
        m_name += ".moved";
        std::cout << "moved " << other.m_name << " -> " << m_name << std::endl;
    }   
};

int main()
{
    std::vector<person> people;
    people.reserve(10);
    
    std::cout << "\ncopy bob (lvalue):" << std::endl;
    person bob{"bob"};
    people.push_back(bob);

    std::cout << "\nmove fred (lvalue):" << std::endl;
    person fred{"fred"};
    people.push_back(std::move(fred));

    std::cout << "\ntemp joe (rvalue):" << std::endl;
    people.push_back(person{"joe"});
    
    std::cout << "\nterminating:" << std::endl;
}

Это дает мне результат, которого я ожидал (в основном, за исключением того, почему содержимое std :: string не «перемещается»?): https://godbolt.org/z/-J_56i

Затем я удаляю std :: vector reserve, чтобы std :: vector должен «расти» по мере добавления элементов. Теперь я получаю то, чего действительно не ожидаю: https://godbolt.org/z/rS6-mj

Теперь я вижу, что bob копируется, а затем перемещается, когда добавляется fred, и затем перемещается снова, когда добавляется joe . У меня создалось впечатление, что std :: vector «перемещается», когда ему нужно перераспределить пространство. Но я думал, что он копирует / перемещает память, а не копирует / перемещает объект за объектом. Я действительно не ожидал, что он вызовет конструктор перемещения.

Теперь, если я удалю перемещение c 'tor, я обнаружу, что bob копируется три раза !: https://godbolt.org/z/_BxnvU Это кажется действительно неэффективным.

С cplusplus.com :

push_back ()

Добавить элемент в конце Добавляет новый элемент в конец вектора после его текущего последнего элемента. Содержимое val копируется (или перемещается) в новый элемент.

Это эффективно увеличивает размер контейнера на единицу, что вызывает автоматическое c перераспределение выделенного пространства хранения, если - и только если - размер нового вектора превосходит текущий размер вектора.

resize ()

Изменяет размер контейнера так, чтобы он содержал n элементов.

Если n меньше текущего размера контейнера, содержимое уменьшается до первых n элементов, удаляя те, что находятся за пределами контейнера (и уничтожая их).

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

Если n также больше, чем текущая емкость контейнера, происходит автоматическое перераспределение выделенного пространства хранения .

Обратите внимание, что эта функция изменяет фактическое содержимое контейнера, вставляя или удаляя из него элементы.

Я думаю, это не совсем описывает, «как» она выполняет перераспределение, но, конечно же, копия в памяти - это самый быстрый способ переместить вектор в его недавно выделенное пространство памяти?

Так почему же вызываются торы копирования / перемещения c, когда std :: vector добавляется вместо копия из памяти?

Примечание / вопрос: (возможно, это должен быть отдельный вопрос): Лично переместите c, чтобы узнать, почему напечатано moved fred -> fred.moved, а не moved -> fred.moved. Похоже, что назначение перемещения std :: string на самом деле не "перемещает" данные ...

1 Ответ

3 голосов
/ 21 июня 2020

Если нужно переместить, будет использовано что-то похожее на std::move(xold.begin(), xold.end(), xnew.begin());. Это зависит от типа значения, и вектор обычно выполняет свое собственное внутреннее размещение new. но он будет двигаться, если может двигаться.

У вашего конструктора перемещения

person(const person &&other) noexcept;

есть недостаток: other не должно быть const, так как ему должно быть разрешено изменение * От 1012 * до украсть его ресурсы. В этом конструкторе перемещения

person(person&& other) noexcept : m_name(std::move(other.m_name)) {}

собственный конструктор перемещения std::string будет делать что-то подобное:

string(string&& other) noexcept : 
    the_size(other.the_size),
    data_ptr(std::exchange(other.data_ptr, nullptr))
{}

Вам также необходимо добавить оператор присваивания перемещения:

person& operator=(person &&other) noexcept;
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...