Страницы

Поиск по вопросам

среда, 26 февраля 2020 г.

C++, std::move(), POD типы и неопределенное поведение

#cpp #move #rvalue #pod #stdmove


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


В каком состоянии (согласно Стандарту) находится объект после перемещения? Корректное,
но неопределенное? Что это значит?
Если объект является объектом POD типа, то каков результат операции std::move(object)?
Относятся ли элементарные типы (int, float, double) к POD типам?
Если переместить объект POD типа, то законно ли дальнейшее использование такого объекта?
Например:

struct Value
{
    int i;
};
Value value;
value.i = 1;
do_something(std::move(value));
// Дальнейшая работа с value.



Буду очень благодарен, если кто-нибудь поможет мне разобраться в этих вопросах.
    


Ответы

Ответ 1



Не "корректное, но неопределенное", а "корректное, но неспефицированное" (!) (http://eel.is/c++draft/definitions#defns.valid). Термин "неопределенный" обладает в языке С++ негативной коннотацией (см. "неопределенное поведение"). У стандарта языка С++ нет никаких причин привносить такой оттенок в описание состояния перемещенного объекта. Поэтому именно "неспефицированное", а не "неопределенное". Упоминание неспецифицированного состояния означает, что все типы разные, и стандарт языка не может однозначно специфицировать конкретные единые правила для всех типов. Каждый тип в рамках семантики перемещения может вести себя по-своему. Язык говорит вам, что за деталями поведения каждого конкретного типа надо обращаться в документацию этого конкретного типа. Документация конкретного типа может частично специфицировать состояние объекта после перемещения, а может и специфицировать его полностью. Более того, даже в рамках конкретного типа состояние объекта после перемещения может зависеть от его состояния до перемещения, то есть от факторов времени выполнения. Не забывайте при этом, что "перемещение", по определению - более широкая концепция, чем "копирование". Обыкновенное "копирование" является, по определению, частным случаем "перемещения". То есть не будет ничего удивительного в том, если вы, обратившись к спецификации какого-либо типа, обнаружите, что для этого типа "перемещение" эквивалентно "копированию". Если, обратившись к спецификации некоего конкретного типа, вы увидите, что для данного типа семантика перемещения эквивалентна семантике копирования, что означает, что исходный объект после перемещения полностью сохраняет свое исходное корректное состояние. Также стоит заметить, что термин "неспефицированное поведение" исторически тоже наделялся явным негативным оттенком: не стоит завязываться в своем коде на такое неспефицированное поведение, как, например, порядок вычисления аргументов функции. Однако в случае семантики перемещения ситуация несколько иная: завязываться на документированные свойства перемещенных объектов вполне можно (и нужно), пока вы не выходите за рамки того, что действительно специфицировано. Для всех типов результатом операции std::move является xvalue того типа, который был передан в качестве аргумента. std::move - это лишь преобразование типов. std::move сам по себе больше ничего не делает. Поэтому никаких особенностей, специфичных для POD, типов у std::move нет. Да, относятся. Да, законно. Для POD типов операция перемещения определена как обыкновенное копирование (см. пункт 1).

Ответ 2



Если это объект класса, то его состояние после перемещения зависит только от того, что написано в перемещающем конструкторе / операторе присваивания. Если это не объект класса (а, например, скаляр), то перемещение делает то же, что и копирование. Исходный объект не меняется. В "корректное, но неопределенное состояние" попадают некоторые стандартные классы после перемещения. Например std::string: "left in valid, but unspecified state". (А некоторые нет. Например std::vector: "guaranteed to be empty()"`.). Корректное, но неопределенное? Что это значит? Нуу, это и значит. Например, строка может оказаться пустой, а может сохранить свое старое значение - это неопределенное состояние. Или например, если .size() возвращает не ноль, то естественно можно спокойно обращаться к строка[0], не опасаясь таинственных вылетов или ошибок - состояние строки корректное. std::move сам по себе ничего не делает, не важно к POD его применили или нет. Он только превращает выражение в rvalue, чтобы его можно было передать в перемещающий конструктор / оператор присваивания (или еще куда-то, где нужен rvalue). Относятся ли элементарные типы (int, float, double) к POD типам? Да. См. определение POD. Если переместить объект POD типа, то законно ли дальнейшее использование такого объекта? Да, объект не изменится. См. (1).

Ответ 3



"Корректное, но неопределенное" Значит закладываться на что либо нельзя, на практике POD просто копируется, другие объекты могут свопаться, кто-то может "обнульть" перемещенный объект. Но после std::move что либо делать с объектом лучше не стоит. На усмотрение компилятора, но обычно ничего не изменяется, но гарантировать, что с изменение версии компилятора ничего не поменяется никто не будет. да Законно, но результат не определен.

Комментариев нет:

Отправить комментарий