Vector:: push_back настаивает на использовании конструктора копирования, хотя предусмотрен механизм перемещения
Я получал странную ошибку от gcc и не могу понять, почему. Я сделал следующий примерный код, чтобы сделать проблему более ясной. В принципе, существует определенный класс, для которого я делаю его конструктором копирования и оператором присваивания копии частным, чтобы случайно вызвать их.
#include <vector>
#include <cstdio>
using std::vector;
class branch
{
public:
int th;
private:
branch( const branch& other );
const branch& operator=( const branch& other );
public:
branch() : th(0) {}
branch( branch&& other )
{
printf( "called! other.th=%d\n", other.th );
}
const branch& operator=( branch&& other )
{
printf( "called! other.th=%d\n", other.th );
return (*this);
}
};
int main()
{
vector<branch> v;
branch a;
v.push_back( std::move(a) );
return 0;
}
Я ожидаю, что этот код будет компилироваться, но он не работает с gcc. На самом деле gcc жалуется, что
"branch:: branch (const branch &) является частной", что, как я понимаю, не следует вызывать.
Оператор присваивания работает, так как если я заменил тело main() на
branch a;
branch b;
b = a;
Он будет компилироваться и выполняться как ожидалось.
Это правильное поведение gcc? Если да, то что не так с вышеуказанным кодом?
Любое предложение полезно для меня. Спасибо!
Ответы
Ответ 1
Попробуйте добавить "noexcept" к объявлению конструктора перемещения.
Я не могу привести этот стандарт, но последние версии gcc требуют, чтобы либо конструктор копирования был общедоступным, либо конструктор перемещения был объявлен "noexcept" . Независимо от квалификатора "noexcept" , если вы сделаете публичный конструктор копирования, он будет вести себя так, как вы ожидаете, во время выполнения.
Ответ 2
В отличие от предыдущего ответа, gcc 4.7 неправильно отклонил этот код, ошибка, которая была исправлена в gcc 4.8.
Полное стандартное поведение для vector<T>::push_back
:
- Если есть только конструктор копирования и конструктор перемещения,
push_back
скопирует его аргумент и предоставит надежную гарантию безопасности. То есть, если push_back не удается из-за исключения, вызванного перераспределением векторного хранилища, исходный вектор останется неизменным и применимым. Это известное поведение из С++ 98, и это также причина беспорядка, который следует.
- Если существует конструктор перемещения
noexcept
для T
, push_back
переместится из его аргумента и предоставит надежную гарантию исключения. Здесь нет сюрпризов.
- Если есть конструктор перемещения, который не является
noexcept
, и есть также конструктор копирования, push_back
скопирует объект и предоставит надежную гарантию безопасности. Это неожиданно на первый взгляд. Хотя push_back
может двигаться здесь, это было бы возможно только за счет жертвования надежной гарантии исключения. Если вы портировали код с С++ 98 на С++ 11, и ваш тип движимый, это тихо изменило бы поведение существующих вызовов push_back
. Чтобы избежать этой ошибки и поддерживать совместимость с кодом С++ 98, С++ 11 возвращается к более медленной копии. Это поведение gcc 4.7. Но есть еще...
- Если есть конструктор перемещения, который не является
noexcept
, но вообще не имеет конструктора копирования, т.е. элемент может быть перемещен и не скопирован - push_back
выполнит этот шаг, но не даст сильного исключения гарантия. Здесь gcc 4.7 поступил не так. В С++ 98 нет push_back
для типов, которые могут перемещаться, но не копироваться. Поэтому жертвовать сильной безопасностью исключений здесь не нарушает существующий код. Вот почему это разрешено, и исходный код на самом деле является законным С++ 11.
Смотрите cppreference.com в push_back
:
Если выбрано исключение, эта функция не имеет эффекта (сильный исключение).
Если конструктор перемещения T не является несущественным, а экземпляр копирования недоступен, вектор будет использовать перемещение броска конструктор. Если он бросает, гарантия отменяется, а эффекты не определено.
Или немного более запутанный §23.3.6.5 из С++ 11 Standard (выделено мной мной):
Вызывает перераспределение, если новый размер больше старой. Если перераспределение не происходит, все итераторы и ссылки до точка ввода остается в силе. Если исключение выдается иначе, чем конструктором копирования, конструктором перемещения, оператором присваивания или переместить оператор назначения T или любую операцию ввода ввода не являются эффектами. Если исключение выбрано конструктором перемещения non-CopyInsertable T, эффекты не определены.
Или, если вам не нравится читать, Скотт Мейер, ведущий родной разговор 2013 года (начало в 0:30:20 с интересной частью примерно в 0:42: 00).