Выбор между vector :: resize () и vector :: reserve ()

151

Я предварительно выделяю память для моей vectorпеременной-члена. Ниже код минимальная часть

class A {
  vector<string> t_Names;
public:
  A () : t_Names(1000) {}
};

Теперь в какой-то момент времени, если t_Names.size()равен 1000. Я намерен увеличить размер на 100. Затем, если оно достигнет 1100, снова увеличьте на 100и так далее.

У меня вопрос, что выбирать между vector::resize()а vector::reserve(). Есть ли лучший выбор в этом сценарии?

Изменить : у меня есть своего рода точная оценка для t_Names. Я оцениваю это быть рядом 700с 800. Однако в определенных (редко) ситуациях он может расти больше, чем 1000.

iammilind
источник
34
Вы понимаете, что это означает, что рост вектора больше не амортизируется постоянным временем, и вы теряете одно из преимуществ использования производительности std::vector.
Доменная печь
1
По теме см. C ++ «Сделано проще: как растут векторы на сайте доктора Доббса».
Jww

Ответы:

262

Эти две функции делают совершенно разные вещи!

resize()Метод (и передавая аргумент в конструктор эквивалентно) будет вставить или удалить соответствующее количество элементов в вектор , чтобы сделать его заданный размер (он имеет необязательный второй аргумент , чтобы определить их значение). Это повлияет на size(), итерация пройдет по всем этим элементам, push_back вставит после них, и вы можете получить к ним прямой доступ, используя operator[].

reserve()Метод только выделяет память, но оставляет его инициализирован. Это только влияет capacity(), но не size()изменится. Для объектов нет значения, потому что к вектору ничего не добавлено. Если вы затем вставите элементы, перераспределение не произойдет, потому что это было сделано заранее, но это единственный эффект.

Так что это зависит от того, что вы хотите. Если вы хотите массив из 1000 элементов по умолчанию, используйте resize(). Если вам нужен массив, в который вы ожидаете вставить 1000 элементов и хотите избежать пары выделений, используйте reserve().

РЕДАКТИРОВАТЬ: комментарий Blastfurnace заставил меня снова прочитать вопрос и понять, что в вашем случае правильный ответ не распределяется вручную. Просто продолжайте вставлять элементы в конце, как вам нужно. Вектор будет автоматически перераспределяться по мере необходимости и будет делать это более эффективно, чем упомянутый ручной способ. Единственный случай, когда это reserve()имеет смысл, - это когда у вас есть достаточно точная оценка общего размера, которая вам будет легко доступна заранее.

РЕДАКТИРОВАТЬ 2: Редактировать вопрос объявления: Если у вас есть начальная оценка, то reserve()эта оценка. Если этого окажется недостаточно, просто дайте вектору сделать свое дело.

Ян Худек
источник
Я отредактировал вопрос. У меня есть определенная оценка для vector.
Iammilind
3
@Jan: ну, это хрупко или нет, в зависимости от того, насколько сложно тебе было поддерживать требуемую собственность. Нечто подобное x.reserve(x.size() + newdata); vector<int>::iterator special_element = get_special_element(x); for (int i = 0; i < newdata; ++i) { if some_function(i, special_element) x.push_back(i); }довольно надежно с точки зрения резервирования места. Я понятия не имею, сколько элементов на самом деле будет добавлено, но у меня есть верхняя граница. Конечно, когда есть сомнения, с векторами вы можете просто использовать индексы вместо итераторов, разница обычно незначительна.
Стив Джессоп
4
Ваша формулировка имеет смысл для тех, кто уже знает правильный ответ, но может легко ввести в заблуждение людей, которым необходимо задать вопрос. "resize () ... вставит заданное количество элементов в вектор" - только true при первом использовании - обычно вставляет разницу между запрошенным числом и существующим ранее size(). «Метод reserve () только выделяет память» - он может выделять или не распределять память в зависимости от того, capacity()достаточно ли он уже, ему также может понадобиться перемещать элементы и освобождать их исходную память. «хочу избежать пары выделений», копий и т. д.
Тони Делрой
19
На самом деле, резервирование до нажатия имеет жизненно важное значение и должно быть использовано. Предположим, что вы кодируете какой-то загрузчик 3d-модели, а модель имеет около 15000 вершин. Если вы попытаетесь отодвинуть каждую вершину во время загрузки без предварительного их выделения, это займет серьезное время. Я лично испытал это, я пытался загрузить модель автомобиля .obj с почти 100000 вершин, это заняло 30 секунд. Затем я произвел рефакторинг кода с использованием предварительного выделения с помощью .reserve (), теперь это занимает 3 секунды. Просто поместив .reserve (100000) в начало кода, вы сэкономите 27 секунд.
Дениз
1
@deniz Это тривиально верно в масштабе 100000, но совсем не верно в масштабе 100-300, где резервирование может быть расточительным, если оно сделано излишне.
deworde
30

resize()не только выделяет память, но и создает столько экземпляров, сколько и желаемый размер, который вы передаете в resize()качестве аргумента. Но reserve()только выделяет память, он не создает экземпляров. То есть,

std::vector<int> v1;
v1.resize(1000); //allocation + instance creation
cout <<(v1.size() == 1000)<< endl;   //prints 1
cout <<(v1.capacity()==1000)<< endl; //prints 1

std::vector<int> v2;
v2.reserve(1000); //only allocation
cout <<(v2.size() == 1000)<< endl;   //prints 0
cout <<(v2.capacity()==1000)<< endl; //prints 1

Выход ( онлайн демо ):

1
1
0
1

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

Наваз
источник
6
После этого reserve(N)мы можем использовать operator []безвредно. верный ?
Iammilind
2
В то время как большинство реализаций будет выделять точную сумму, по которой вы запрашиваете reserve, спецификация требует, чтобы она выделяла, по крайней мере, столько, поэтому некоторые реализации могут округляться до некоторой границы и, таким образом, показывать более высокую емкость, чем 1000.
Ян Худек
16
@iammilind: Нет, если индекс больше или равен v.size(). Обратите внимание, что reserve(N)не меняется size()вектор.
Наваз
5
@iammilind: неверно. После вызова reSERVE записи не добавляются, достаточно только памяти для их добавления.
Ян Худек
2

Из вашего описания похоже, что вы хотите «зарезервировать» выделенное пространство для хранения вектора t_Names.

Обратите внимание, что resizeинициализируйте только что выделенный вектор, который reserveпросто выделяет, но не создает. Следовательно, «резерв» намного быстрее, чем «изменение размера»

Вы можете обратиться к документации относительно разницы размеров и резерва

падение
источник
1
Пожалуйста, обратитесь сюда вместо этого: вектор и емкость ( почему? )
сехе
1
Спасибо за добавление ссылки, sehe
dip
2

резервировать, когда вы не хотите, чтобы объекты инициализировались при резервировании. Кроме того, при изменении размера вы можете предпочесть логически дифференцировать и отслеживать его количество в зависимости от его количества использования. поэтому в интерфейсе есть поведенческая разница - вектор будет представлять то же количество элементов при резервировании и будет на 100 элементов больше при изменении размера в вашем сценарии.

Есть ли лучший выбор в этом сценарии?

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

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

джастин
источник
« резервируйте, если вы не хотите, чтобы объекты инициализировались при резервировании. » Правильная формулировка - это когда вы не хотите, чтобы объекты существовали . Это не похоже на неинициализированный массив тривиально конструируемого типа, где объекты не могут быть прочитаны, но могут быть назначены; скорее, зарезервирована только память, но в ней нет объектов, поэтому к ним нельзя получить доступ, используя operator[]что-либо.
underscore_d