Некоторое уточнение по ссылкам rvalue

Сначала: где определены std::move и std::forward? Я знаю, что они делают, но я не могу найти доказательства того, что для их включения требуется любой стандартный заголовок. В gcc44 иногда std::move доступен, а иногда и нет, поэтому полезная директива include будет полезна.

При реализации семантики перемещения источник предположительно остается в состоянии undefined. Должно ли это состояние быть действительным для объекта? Очевидно, что вы должны иметь возможность вызвать деструктор объекта и иметь возможность назначать ему любые средства, которые предоставляет класс. Но должны ли действовать другие операции? Я полагаю, что я спрашиваю: если ваш класс гарантирует определенные инварианты, следует ли вам стремиться к принудительному применению этих инвариантов, когда пользователь сказал, что они больше не заботятся о них?

Далее: когда вы не заботитесь о семантике перемещения, существуют ли какие-либо ограничения, которые могут привести к тому, что неконстантная ссылка будет предпочтительнее ссылки rvalue при работе с параметрами функции? void function(T&); over void function(T&&); С точки зрения вызывающего абонента время от времени возможность временных функций иногда бывает полезной, поэтому кажется, что нужно предоставлять эту опцию всякий раз, когда это возможно. И ссылки rvalue сами являются lvalues, поэтому вы не можете непреднамеренно вызвать конструктор move вместо конструктора-копии или что-то в этом роде. Я не вижу недостатка, но я уверен, что есть один.

Что подводит меня к моему последнему вопросу. Вы по-прежнему не можете привязывать временные ссылки к неконстантным ссылкам. Но вы можете связать их с неконстантными ссылками rvalue. И вы можете передать эту ссылку как неконстантную ссылку в другой функции.

void function1(int& r) { r++; }
void function2(int&& r) { function1(r); }
int main() { 
    function1(5); //bad
    function2(5); //good
}

Помимо того, что он ничего не делает, есть ли что-то не так с этим кодом? Моя кишка говорит, конечно, нет, так как изменение ссылок на ссылки - это вся суть их существования. И если переданное значение законно const, компилятор поймает его и кричит на вас. Но, судя по всему, это путаница механизма, который предположительно был создан по какой-то причине, поэтому я просто хотел бы подтвердить, что я не делаю ничего глупого.

Ответ 1

Сначала: где std:: move и std:: forward определены?

См. 20.3 Компоненты Utility, <utility>.


При реализации семантики перемещения источник предположительно остается в состоянии undefined. Должно ли это состояние быть действительным для объекта?

Очевидно, что объект все равно должен быть разрушительным. Но, кроме того, я считаю, что это хорошая идея, которую можно по-прежнему назначать. Стандарт говорит для объектов, которые удовлетворяют "MoveConstructible" и "MoveAssignable":

[Примечание: rv остается действительным объектом. Его состояние неуточнено. - конечная нота]

Это будет означать, я думаю, что объект все еще может участвовать в любой операции, которая не содержит никаких предварительных условий. Это включает CopyConstructible, CopyAssignable, Destructible и другие вещи. Обратите внимание, что это не потребует ничего для ваших собственных объектов с точки зрения основного языка. Требования выполняются только после того, как вы касаетесь стандартных компонентов библиотеки, которые определяют эти требования.


Далее: когда вы не заботитесь о семантике перемещения, существуют ли какие-либо ограничения, которые могут привести к тому, что неконстантная ссылка будет предпочтительнее ссылки rvalue при работе с параметрами функции?

Это, к сожалению, принципиально зависит от того, находится ли параметр в шаблоне функции и использует параметр шаблона:

void f(int const&); // takes all lvalues and const rvalues
void f(int&&); // can only accept nonconst rvalues

Однако для шаблона функции

template<typename T> void f(T const&);
template<typename T> void f(T&&);

Вы не можете этого сказать, потому что второй шаблон после вызова с lvalue имеет в качестве параметра синтезированного объявления тип U& для nonconst lvalues ​​(и лучше подходит) и U const& для const lvalues ​​(и быть неоднозначным). Насколько я знаю, нет частичного правила упорядочения, чтобы устранить эту вторую двусмысленность. Однако этот уже известен.

-- Изменить --

Несмотря на этот отчет о проблеме, я не думаю, что эти два шаблона неоднозначны. Частичное упорядочение сделает первый шаблон более специализированным, потому что после удаления ссылочных модификаторов и const мы обнаружим, что оба типа одинаковы, а затем обратите внимание, что первый шаблон имел ссылку на const. В Стандарте говорится (14.9.2.4)

Если для данного типа вывод преуспевает в обоих направлениях (т.е. типы идентичны после вышеперечисленных преобразований), и если тип из шаблона аргумента более качественный, чем тип из шаблона параметра ( как описано выше), этот тип считается более специализированным, чем другой.

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

Это делает шаблон T const& победителем частичного упорядочения (и GCC действительно прав, чтобы выбрать его).

-- Изменить конец --


Что подводит меня к моему последнему вопросу. Вы по-прежнему не можете привязывать временные ссылки к неконстантным ссылкам. Но вы можете связать их с неконстантными ссылками rvalue.

Это хорошо объяснено в в этой статье. Второй вызов с использованием function2 принимает только неконкурентные значения. Остальная часть программы не заметит, если они будут изменены, потому что они больше не смогут получить доступ к этим rvalues! И 5, который вы передаете, не является типом класса, поэтому создается скрытое временное и затем передается в ссылку int&& rvalue. Код, вызывающий function2, не сможет получить доступ к этому скрытому объекту здесь, поэтому он не заметит никаких изменений.

Другая ситуация заключается в том, что вы делаете это:

SomeComplexObject o;
function2(move(o));

Вы явно запросили, чтобы o был перемещен, поэтому он будет изменен в соответствии со спецификацией перемещения. Однако перемещение - это логически не изменяющаяся операция (см. Статью). Это означает, что вы перемещаетесь или не должны быть видимыми из вызывающего кода:

SomeComplexObject o;
moveit(o); // #1
o = foo;

Если вы удалите строку, которая будет двигаться, поведение будет по-прежнему тем же, потому что оно все равно перезаписывается. Это, однако, означает, что код, который использует значение o после того, как он был перенесен, плох, потому что он нарушает этот неявный контракт между moveit и вызывающим кодом. Таким образом, в стандарте не указывается конкретное значение перемещенного из контейнера.

Ответ 2

где указаны std:: move и std:: forward?

std::move и std::forward объявлены в <utility>. См. Резюме в начале раздела 20.3 [Утилита].

При реализации семантики перемещения источник предположительно остается в состоянии undefined.

Конечно, это зависит от того, как вы реализуете оператор move-constructor и оператор move-assign. Однако, если вы хотите использовать свои объекты в стандартных контейнерах, вы должны следовать концепциям MoveConstructible и MoveAssignable, в которых говорится, что объект остается в силе, но остается в неопределенном состоянии, то есть вы определенно можете его уничтожить.

Ответ 3

включен утилита


Здесь - статья, которую я прочитал о rvalues.

Я не могу помочь вам отдохнуть, извините.