Как включить семантику перемещения при добавлении пользовательских объектов в вектор? - PullRequest
5 голосов
/ 03 мая 2019

Код ниже передает объекты, содержащие большие векторы, в вектор. Я хочу, чтобы это было быстрым. Нужно ли приводить test к значению при вызове push_back? Нужно ли указывать компилятору, как перемещать экземпляры struct Test? Или все это происходит автоматически?

int main()
{
    struct Test
    {
        std::vector<size_t> vals;
        double sum;
    };
    std::vector<Test> vecOfTest;
    vecOfTest.reserve(100000);

    for (size_t i = 0; i < 100000; i++)
    {
        Test test{};
        test.vals.reserve(i);
        for (size_t j = 0; j < i; j++)
        {
            test.vals.push_back(j);
            test.sum += j;
        }
        vecOfTest.push_back(test);
    }


    return 0;
}

Ответы [ 2 ]

4 голосов
/ 03 мая 2019

Я хочу, чтобы это было быстродействующим

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

#include <vector>
#include <iostream>
#include <numeric>

struct Test
{
    std::vector<size_t> vals;
    double sum = 0; // initialing is a good idea
    Test(const size_t v, const double res) // provide constructor(appropriate one)
        : vals(v), // tell the size of the vals directly in the constructor
          sum(res) 
    {}
};

int main()
{

    std::vector<Test> vecOfTest;
    vecOfTest.reserve(100000);

    for (size_t i = 0; i < 100000; i++)
    {
        // Test& last_test = vecOfTest.emplace_back() needs C++17, otherwise
        // use std::vector::back()
        auto& last_test = vecOfTest.emplace_back(   // create the Test object in place and take the reference to it
            i,                     // tell the size of vals in newly creating Test object
            ((i - 1) * i) / 2.0    // ((j-1) * j)/2 = sum from 0 to j-1
        );
        std::iota(std::begin(last_test.vals), std::end(last_test.vals), static_cast<size_t>(0)); // populate, 0 to size of vals
    }
    return 0;
}
2 голосов
/ 03 мая 2019

Ваша структура Test не определяет никаких специальных функций-членов (конструктор копирования, деструктор и т. Д.). Это означает, что оператор назначения перемещения по умолчанию и конструктор копирования перемещения по умолчанию генерируются автоматически, и они будут перемещать каждый элемент данных структура. Таким образом, Test является подвижным типом, и он выигрывает от этого, поскольку vector<size_t> является подвижным элементом данных.

Однако перемещения не выполняются автоматически, потому что перемещение от объекта меняет его. Даже если вы думаете, что это:

    vecOfTest.push_back(test);
}

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

vecOfTest.push_back(std::move(test));

Единственный случай, когда вам не нужно двигаться, это когда движение мешало бы выбору. Например, в функции, которая возвращает Test, это:

Test test;
return std::move(test);

будет двигаться, но лучше не делать. Лучше:

return test;

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

vecOfTest.push_back(test);

не подходит для исключения, и поэтому неявное движение никогда не произойдет.

...