std::copy_n не изменяет размер вектора назначения

Если я резервирую место для вектора, а затем копирую в него некоторые значения с помощью std::copy_n(), я получаю корректно скопированные и доступные значения, но размер вектора по-прежнему равен нулю. Это ожидаемое поведение? Должен ли я вместо этого изменить размер вектора, даже если он не так эффективен?

#include <algorithm>
#include <iostream>
#include <vector>

int main()
{
    std::vector<double> src, dest;

    for(double x = 0.0; x < 100.0; ++x)
        src.push_back(x);

    dest.reserve(src.size());

    std::copy_n(src.cbegin(), src.size(), dest.begin());

    std::cout << "src.size() = " << src.size() << std::endl;
    std::cout << "dest.size() = " << dest.size() << std::endl;

    for(size_t i = 0; i < src.size(); ++i)
        std::cout << dest[i] << "  ";

}

Протестированы компиляторы: clang, gcc, Visual C++


person Pietro    schedule 04.02.2017    source источник
comment
Я думаю, что reserve() влияет только на емкость, а не на размер.   -  person    schedule 04.02.2017
comment
@RawN, да, но я ожидал, что copy_n обновит размер.   -  person Pietro    schedule 04.02.2017
comment
Вы знаете, что можете просто сделать dest=src? Вы знаете функцию присваивания вектора?   -  person    schedule 05.02.2017
comment
@manni66: Да. Я упростил свой код, но src может не быть std::vector.   -  person Pietro    schedule 05.02.2017
comment
@Pietro assign имеет перегрузку для итераторов.   -  person    schedule 06.02.2017
comment
@manni66: Значит, copy_n и operator= эквивалентны с этой точки зрения? Есть ли различия в производительности/реализации?   -  person Pietro    schedule 06.02.2017
comment
@Pietro, очевидно, это не так: operator= присваивает один вектор другому, copy_n копирует один диапазон в другой. Я бы предположил, что operator= ist быстрее. Это определенно безопаснее. vector::assign имеет больше общего с copy_n, но он никогда не будет писать за пределами выделенного пространства.   -  person    schedule 07.02.2017


Ответы (4)


но размер вектора по-прежнему равен нулю

std::copy_n не изменит размер контейнера, просто скопируйте значение и выполните итераторы; у него даже нет никакой информации о контейнере. Таким образом, код имеет неопределенное поведение, даже если кажется, что он работает нормально.

Должен ли я вместо этого изменить размер вектора, даже если он не так эффективен?

Да, вы можете использовать std::vector::resize вместо std::vector::reserve для решения проблемы. Как вы могли подумать, это означает, что все элементы сначала будут построены resize, а затем назначены copy_n.

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

dest.reserve(src.size());
std::copy_n(src.cbegin(), src.size(), std::back_inserter(dest));
person songyuanyao    schedule 04.02.2017
comment
Разве std::back_inserter не намного медленнее, чем std::vetor::resize с последующим копированием? - person Kamil Koczurek; 04.02.2017
comment
@KamilKoczurek Нет. Он вызовет push_back контейнера для непосредственного добавления элементов; вместо того, чтобы создавать все элементы с помощью resize, а затем назначать их. - person songyuanyao; 04.02.2017
comment
Правильно, я не понял, что вы вызываете std::vetor::reserve, так что нет необходимости перераспределять память, мой плохой. - person Kamil Koczurek; 04.02.2017
comment
@KamilKoczurek Все в порядке. Действительно, я не написал это явно в первый раз в своем ответе. - person songyuanyao; 04.02.2017

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

Поэтому, когда вы вызываете std::copy_n, вы должны указать диапазон, достаточно большой для хранения результата. Это означает настройку диапазона с помощью dest.resize(src.size());, а не просто выделение памяти с помощью dest.reserve(std.size());.

В качестве альтернативы вы можете указать диапазон, который знает, что он прикреплен к контейнеру и что ему нужно настроить размер, вызвав алгоритм с std::back_inserter(dest) вместо dest.begin().

person Pete Becker    schedule 04.02.2017

std::vector имеет размер и емкость. Он резервирует немного больше места, чем необходимо, для более быстрой вставки. reserve позволяет указать эту емкость, а resize изменяет реальный размер вектора.

person The Techel    schedule 04.02.2017

Ваш dest выделил память для хранения элементов после того, как вы вызвали reserve, но она не вызывает конструкторы и на самом деле пуста, поэтому ваш код ведет к UB. Используйте resize для создания этих элементов, и тогда все будет в порядке.

dest.resize(src.size());
std::copy_n(src.cbegin(), src.size(), dest.begin());

std::cout << "src.size() = " << src.size() << std::endl;
std::cout << "dest.size() = " << dest.size() << std::endl;

for(size_t i = 0; i < src.size(); ++i)
    std::cout << dest[i] << "  ";
person Kamil Koczurek    schedule 04.02.2017