Почему resize() вызывает копирование, а не перемещение содержимого вектора при превышении емкости?

Данный класс X ниже (специальные функции-члены, отличные от явно определенной, не относятся к этому эксперименту):

struct X
{
    X() { }
    X(int) { }
    X(X const&) { std::cout << "X(X const&)" << std::endl; }
    X(X&&) { std::cout << "X(X&&)" << std::endl; }
};

Следующая программа создает вектор объектов типа X и изменяет его размер так, чтобы его емкость была превышена, а перераспределение было принудительным:

#include <iostream>
#include <vector>

int main()
{
    std::vector<X> v(5);
    v.resize(v.capacity() + 1);
}

Поскольку класс X предоставляет конструктор перемещения, я ожидаю, что предыдущее содержимое вектора будет перемещено в новое хранилище после перераспределения. Удивительно, но это не так, и я получаю следующий вывод:

X(X const&)
X(X const&)
X(X const&)
X(X const&)
X(X const&)

Почему?


person Andy Prowl    schedule 31.03.2013    source источник
comment
Также обратите внимание, что Microsoft не соблюдает это правило, поэтому этот код, скомпилированный с помощью Visual C++ (или clang в Windows ) вызовет конструктор перемещения и, таким образом, может оставить вас с неработающими (пустыми) элементами в std::vector в случае сбоя изменения размера.   -  person Denis    schedule 06.07.2017


Ответы (2)


В параграфе 23.3.6.3/14 стандарта С++ 11 указывается (о функции-члене resize() шаблона класса vector<>):

Примечания. Если исключение выдается не конструктором перемещения не-CopyInsertable T, эффектов не будет.

Другими словами, это означает, что для X (то есть CopyInsertable) resize() предлагает сильную гарантию: он либо завершается успешно, либо оставляет состояние вектора без изменений.

Чтобы удовлетворить эту гарантию, реализации обычно используют copy-and- идиома подкачки: если конструктор копирования X сбрасывает, мы еще не изменили содержимое исходного вектора, поэтому обещание сохраняется.

Однако, если предыдущее содержимое вектора было перемещено в новое хранилище, а не скопировано и конструктор перемещения выбросил, то мы бы необратимо изменили исходное содержимое вектора. вектор.

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

С небольшим изменением в определении конструктора перемещения X (пометив его как noexcept), фактически выход программы теперь ожидаемый.:

struct X
{
    X() { }
    X(int) { }
    X(X const&) { std::cout << "X(X const&)" << std::endl; }
    X(X&&) noexcept { std::cout << "X(X&&)" << std::endl; }
//         ^^^^^^^^
};
person Andy Prowl    schedule 31.03.2013
comment
Что происходит? Вопрос в блоге? :-) - person Kerrek SB; 31.03.2013
comment
@KerrekSB: мне сказали, что вопросы с самостоятельными ответами — это нормально, я подумал, что это будет интересно :) - person Andy Prowl; 31.03.2013
comment
@AndyProwl - я видел, как кто-то еще пытался сделать это, и они были забиты дубинками... - person chue x; 31.03.2013
comment
Ну, они есть, но обычно только в том случае, если у вас есть реальная проблема, а позже вы сами ее решаете. Если вы уже знаете ответ заранее, это немного подозрительно. Я полагаю, вы можете пометить вопрос как FAQ и Community Wiki, если вы просто хотите поделиться некоторой информацией. - person Kerrek SB; 31.03.2013
comment
@KerrekSB: Но вот что случилось. У меня была проблема, я понял, что происходит, и я решил поделиться ею. См., например, это. Я думал, что все в порядке. - person Andy Prowl; 31.03.2013
comment
@Andy - Возможно, вы не были здесь достаточно долго, чтобы увидеть длинные и запутанные ответы с тегом [c++-faq]. Есть несколько очень длинных и чрезвычайно подробных ответов на вопросы, которые никто никогда не задавал. Я добавил этот тег в свой игнор-лист. :-) - person Bo Persson; 31.03.2013
comment
@BoPersson: я не утверждал, что это стоит того, чтобы быть часто задаваемым вопросом, я просто нашел проблему, ответил на нее и решил поделиться ею. Я не знал, что это обман, иначе я бы не опубликовал это. Я делал то же самое раньше, по примеру других, и никто не жаловался. Теперь я знаю, что это не очень хорошая идея - person Andy Prowl; 31.03.2013
comment
@Энди - Это может быть хорошей идеей, но некоторые люди могут также увидеть это, когда вы пытаетесь придумать вопрос, потому что у вас есть хороший ответ (что и произошло ранее). Если никто никогда не спрашивал об этом раньше, просто может быть (в данном случае маловероятно), что все знают ответ. Я сделал. :-) - person Bo Persson; 31.03.2013
comment
@Bo: Хорошо, спасибо за совет :) - person Andy Prowl; 01.04.2013
comment
+1 Пожалуйста, продолжайте отвечать на свои вопросы. Я многое узнаю из ваших ответов. - person Olaf Dietsche; 11.04.2013
comment
@OlafDietsche: Хорошо, спасибо за поддержку. Я рад, что кто-то нашел это полезным. - person Andy Prowl; 11.04.2013

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

Только если вы знаете, что конструктор перемещения не вызывает исключения, вы можете безопасно перемещать элементы в новое место. Для этого объявите конструктор перемещения noexcept.

person Kerrek SB    schedule 31.03.2013