измените размер по сравнению с push_back в станд.:: вектор: это избегает ненужного присвоения копии?

При вызове метода push_back от std::vector, его размер увеличен одним, подразумевая в создании нового экземпляра, и затем параметр, который Вы передаете, будет скопирован в этот недавно созданный элемент, правильно? Пример:

myVector.push_back(MyVectorElement());

Хорошо затем, если я хочу увеличить размер вектора с элементом просто с помощью его значений по умолчанию, не был бы это быть лучше использовать resize метод вместо этого? Я имею в виду как это:

myVector.resize(myVector.size() + 1);

Насколько я вижу, это выполнило бы точно то же самое, но избежит совершенно ненужной копии присвоения атрибутов элемента.

Это рассуждает корректный, или я пропускаю что-то?

22
задан Gumbo 12 October 2011 в 12:40
поделиться

11 ответов

По крайней мере, с GCC не имеет значения, какой вы используете (результаты ниже). Однако, если вы дойдете до того момента, когда вам придется об этом беспокоиться, вам следует использовать указатели или (что еще лучше) некоторую форму интеллектуальных указателей. . Я бы, конечно, порекомендовал те, что в библиотеке ускорения .

Если вы хотите знать, что лучше использовать на практике, я бы предложил либо push_back , либо зарезервировать asize изменяет размер вектора каждый раз при его вызове, если только он не совпадает с размером запрошенного. push_back и резервный изменят размер вектора только при необходимости. Это хорошо, так как если вы хотите изменить размер вектора до size + 1 , он может уже иметь размер size + 20 , поэтому вызов изменения размера не принесет никакой пользы.

Тестовый код

#include <iostream>
#include <vector>

class Elem{
    public:
        Elem(){
            std::cout << "Construct\n";
        }
        Elem(const Elem& e){
            std::cout << "Copy\n";
        }
        ~Elem(){
            std::cout << "Destruct\n";
        }   
};


int main(int argc, char* argv[]){
    {
        std::cout << "1\n";
        std::vector<Elem> v;
        v.push_back(Elem());
    }

    {
        std::cout << "\n2\n";
        std::vector<Elem> v;
        v.resize(v.size()+1);
    }
}

Тестовый результат

1
Construct
Copy
Destruct
Destruct

2
Construct
Copy
Destruct
Destruct
19
ответ дан 29 November 2019 в 03:50
поделиться

Когда вы выполняете push_back (), метод проверяет нижележащую область хранения, чтобы увидеть, требуется ли место. Если необходимо пространство, он выделит новую непрерывную область для всех элементов и скопирует данные в новую область.

НО : Размер вновь выделенного пространства не только на один элемент больше. Он использует изящный небольшой алгоритм для увеличения пространства (я не думаю, что алгоритм определен как часть стандарта, но обычно он удваивает выделенное пространство). Таким образом, если вы помещаете большое количество элементов, только небольшой процент из них фактически приводит к перераспределению основного пространства.

Для фактического увеличения выделенного пространства вручную у вас есть два варианта:

  • резерв ()

    Это увеличивает базовое пространство для хранения без добавления элементов в вектор. Таким образом, уменьшается вероятность того, что будущие вызовы push_back () потребуют увеличения пространства.

  • resize ()

    Фактически добавляет / удаляет элементы вектора, чтобы сделать его правильным размером.

  • capacity ()

    Общее количество элементов, которые могут быть сохранены до того, как базовое хранилище потребуется быть перераспределенным. Таким образом, если capacity ()> size () , push_back не приведет к перераспределению векторного хранилища.

4
ответ дан 29 November 2019 в 03:50
поделиться

Я считаю, что myVector.push_back (MyVectorElement ()); гораздо более прямолинейный и легкий для чтения.

Дело в том, что resize не работает. не просто изменить размер массива и элементов конструкции по умолчанию в этих местах; это именно то, что по умолчанию. Фактически он принимает второй параметр, который является копией каждого нового элемента, и по умолчанию он равен T () . По сути, два ваших примера кода в точности одинаковы.

16
ответ дан 29 November 2019 в 03:50
поделиться

В EA (Electronic Arts) это сочли такой большой проблемой, что они написали свою собственную версию STL, EASTL , которая, среди прочего, включает ] push_back (void) в их классе vector .

6
ответ дан 29 November 2019 в 03:50
поделиться

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

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

3
ответ дан 29 November 2019 в 03:50
поделиться

Когда вы вызываете push_back , предполагая, что изменение размера базового хранилища не требуется, класс vector будет использовать оператор «размещение нового» для копировать-конструировать новые элементы на месте. Элементы в векторе будут не сконструированы по умолчанию перед копированием.

Когда вы вызываете resize , происходит почти такая же последовательность. вектор выделяет память, а затем копирует значение по умолчанию путем размещения new в каждое новое место.

Конструкция выглядит следующим образом:

::new (p) _T1(_Val);

Где p - указатель на векторную память , _T1 - это тип, хранящийся в векторе, а _Val - параметр «значение по умолчанию» (который по умолчанию равен _T1 () ).

Коротко, resize и push_back делают то же самое, и разница в скорости будет из-за множественных внутренних распределений, множественных проверок границ массива и накладных расходов на вызов функций. Время и сложность памяти будут такими же.

2
ответ дан 29 November 2019 в 03:50
поделиться

По крайней мере, с GCC, не имеет значения, какой вы используете (Результаты ниже). Однако, если вы дойдете до того момента, когда вам придется об этом беспокоиться, вам следует использовать указатели или (что еще лучше) некоторую форму интеллектуальных указателей. . Я бы, конечно, порекомендовал те, что в библиотеке ускорения .

Если вы хотите знать, что лучше использовать на практике, я бы предложил либо push_back , либо зарезервировать as resize будет изменять размер вектора каждый раз, когда он вызывается, если только он не совпадает с запрошенным размером. push_back и резервный изменят размер вектора только при необходимости. Это хорошо, так как если вы хотите изменить размер вектора до size + 1 , он может уже иметь размер size + 20 , поэтому вызов изменения размера не принесет никакой пользы.

1
ответ дан 29 November 2019 в 03:50
поделиться

Я подозреваю, что реальный ответ в значительной степени зависит от реализации STL и используемого компилятора, однако функция "изменения размера" имеет прототип ( ref )

void resize( size_type num, TYPE val = TYPE() );

, который подразумевает, что val создается по умолчанию и копируется во вновь выделенное (или, возможно, ранее выделенное, но неиспользуемое) пространство посредством размещения new и конструктора копирования. Таким образом, обе операции требуют одинаковой последовательности действий:

  1. Вызвать конструктор по умолчанию
  2. Выделить пространство
  3. Инициализировать с помощью конструктора копирования

Вероятно, лучше отложить выполнение более ясного и универсального (с точки зрения контейнеров STL) ) push_back, а не применять преждевременную оптимизацию - если профилировщик выделяет push_back как горячую точку, наиболее вероятной причиной является выделение памяти, которое лучше всего разрешается путем разумного использования резерва.

0
ответ дан 29 November 2019 в 03:50
поделиться
myVector.resize(myVector.size() + 1);

вызовет пустой конструктор MyVectorElement. Что вы пытаетесь достичь? Чтобы зарезервировать место в векторе (и сэкономить накладные расходы на выделение памяти), существует метод reserve (). Вы не можете избежать конструктора.

2
ответ дан 29 November 2019 в 03:50
поделиться

Перспектива C ++ 0x относительно тестирования код принятого ответа Якоби:

  1. Добавить конструктор move в класс:

     Elem (Elem && e) {std :: cout << "Move \ n"; }
    

    С помощью gcc я получаю «Переместить» вместо «Копировать» в качестве вывода для push_back , что в целом намного эффективнее.

  2. Даже немного лучше с разместить операций (взять то же аргументы в качестве конструктора):

    v.emplace_back ()

Результат теста:

1
Construct
Destruct

2
Construct
Copy
Destruct
Destruct
6
ответ дан 29 November 2019 в 03:50
поделиться

Obviously you're worried about efficiency and performance.

std::vector is actually a very good performer. Use the reserve method to preallocate space if you know roughly how big it might get. Obviously this is at the expense of potentially wasted memory, but it can have quite a performance impact if you're using push_back a lot.

I believe it's implementation dependent as to how much memory is reserved up front for a vector if any at all, or how much is reserved for future use as elements are added. Worst case scenario is what you're stating - only growing by one element at a time.

Try some performance testing in your app by comparing without the reserve and with it.

2
ответ дан 29 November 2019 в 03:50
поделиться
Другие вопросы по тегам:

Похожие вопросы: