std :: vector :: push_back () не компилируется в MSVC для объекта с удаленным конструктором перемещения - PullRequest
0 голосов
/ 23 октября 2018

У меня есть класс с удаленным конструктором перемещения, и когда я пытаюсь вызвать std :: vector :: push_back () в MSVC (v.15.8.7 Visual C ++ 2017), я получаю сообщение о том, что пытаюсь получить доступудаленный конструктор перемещения.Однако, если я определяю конструктор перемещения, код компилируется, но конструктор перемещения никогда не вызывается.Обе версии компилируются и запускаются в gcc (v. 5.4), как ожидается.

Вот упрощенный пример:

#include <iostream>
#include <vector>

struct A
{
public:
    A() { std::cout << "ctor-dflt" << std::endl; }
    A(const A&) { std::cout << "ctor-copy" << std::endl; }
    A& operator=(const A&) { std::cout << "asgn-copy" << std::endl; return *this; }
    A(A&&) = delete;
    A& operator=(A&& other) = delete;
    ~A() { std::cout << "dtor" << std::endl; }
};


int main()
{
    std::vector<A> v{};
    A a;
    v.push_back(a);
}

, который при компиляции в Visual Studio выдает следующую ошибку:

error C2280: 'A::A(A &&)': attempting to reference a deleted function  

Если, однако, я определяю конструктор перемещения вместо его удаления

 A(A&&) { std::cout << "ctor-move" << std::endl; }

, все компилируется и запускается со следующим выводом:

ctor-dflt
ctor-copy
dtor
dtor

, как и ожидалось.Нет вызовов конструктору перемещения.(Живой код: https://rextester.com/XWWA51341)

Более того, обе версии отлично работают на gcc. (Живой код: https://rextester.com/FMQERO10656)

Итак, мой вопрос, почему не вызывается std:: vector :: push_back () для неподвижного объекта, компилируемого в MSVC, даже если конструктор перемещения, по-видимому, никогда не вызывается?

Ответы [ 2 ]

0 голосов
/ 23 октября 2018

Это неопределенное поведение, поэтому и gcc, и MSVC верны.

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

Мы можем увидеть причину, начав с [container.requirements.general] Таблица 88, который говорит нам, что push_back требует T быть CopyInsertable :

Требуется: T должен быть CopyInsertable в x

имы видим, что CopyInsertable требует MoveInsertable [container.requirements # general] p15 :

T - CopyInsertable в X означает, что, в дополнение к тому, что T - MoveInsertable в X ...

В этом случае A не MoveInsertable .

Мы можем видеть, что это неопределенное поведениеизменяя значение на [res.on.required] p1 :

Нарушение предварительных условий, указанных в функции Requires: параграф приводит к неопределенному поведению, если функция Throws: абзац не определяет бросокисключениеВ случае нарушения предварительного условия.

[res.on.required] подпадает под Требования для всей библиотеки .

В этомв случае, если у нас нет параграфа throws, поэтому мы имеем неопределенное поведение, которое не требует диагностики, как мы можем видеть из его definition :

поведения, для которого этот международный стандартне предъявляет никаких требований ....

Обратите внимание, это очень отличается от того, что плохо сформирован, что требует диагностики, я объясню все детали в мой ответ здесь .

0 голосов
/ 23 октября 2018

std::vector<T>::push_back() требует T для удовлетворения концепции MoveInsertable (которая фактически включает в себя распределитель Alloc).Это связано с тем, что push_back в векторе может потребоваться увеличить вектор, переместив (или скопировав) все элементы, которые в нем уже есть.

Если вы объявите ход c'or для T как удаленный,тогда, по крайней мере для распределителя по умолчанию (std::allocator<T>), T больше не будет MoveInsertable .Обратите внимание, что это отличается от случая, когда конструктор перемещения не был объявлен, например, потому что неявно генерировалась только копия c'or, или потому что была объявлена ​​только копия c'tor, и в этом случае тип по-прежнему MoveInsertable , но на самом деле вызывается копия c'tor (это немного нелогично, TBH).

Причина, по которой на самом деле никогда не вызывается move c'tor, заключается в том, что вы вставляете только один элемент,и поэтому перемещение существующих элементов не требуется во время выполнения.Важно, что ваш аргумент push_back сам по себе является lvalue и поэтому ни в коем случае не копируется и не перемещается.

ОБНОВЛЕНИЕ : мне пришлось взглянуть на это подробнеевнимательно (спасибо отзывам в комментариях).Версии MSVC, которые отклоняют код, на самом деле правильны (очевидно, и 2015, и до 2018 года делают это, но 2017 принимает код).Поскольку вы звоните push_back(const T&), T должен быть CopyInsertable .Однако CopyInsertable определяется как строгое подмножество MoveInsertable .Поскольку ваш тип не является MoveInsertable , он также не является CopyInsertable по определению (обратите внимание, что, как объяснено выше, тип может удовлетворять обеим концепциям, даже если он только копируемый, так какдо тех пор, пока ход c'or явно не удален).

Разумеется, возникает ряд вопросов: (A) Почему GCC, Clang и некоторые версии MSVC все равно принимают код, и (B)нарушают ли они стандарт?

Что касается (A), то нет другого способа узнать, кроме как поговорить с разработчиками стандартной библиотеки или посмотреть на исходный код ... я думаю, что это простонет необходимости осуществлять эту проверку, если все, что вас волнует, - это заставить юридические программы работать.Перемещение существующих элементов во время push_back (или reserve и т. Д.) Может происходить любым из трех способов в соответствии со стандартом:

  • Если std::is_nothrow_move_constructible_v<T>, то элементы не перемещаются (и операция строго безопасна для исключений).
  • В противном случае, если T равен CopyInsertable , то элементы копируются (и операция строго безопасна для исключений).
  • В противном случае элементы перемещаются (и эффекты исключений, возникающих при перемещении c'or, не определены).

Поскольку ваш тип не nothrow move c'tible, но имеет копию c'torВторой вариант может быть выбран.Это снисходительно, поскольку для MoveInsertable проверка не производится.Это может быть недосмотр реализации, или я мог быть намеренно проигнорирован.(Если тип не MoveInsertable , то весь вызов некорректен, поэтому эта пропущенная проверка не влияет на правильно сформированные программы.)

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...