Некоторые уточнения по ссылкам - PullRequest
10 голосов
/ 05 апреля 2010

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

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

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

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

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

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

Ответы [ 3 ]

10 голосов
/ 05 апреля 2010

Первое: где определены std :: move и std :: forward?

См. 20.3 Служебные компоненты, <utility>.


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

Очевидно, что объект все еще должен быть разрушаемым. Но, кроме того, я думаю, что это хорошая идея - быть назначаемым. Стандарт говорит для объектов, которые удовлетворяют "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& для неконстантных l-значений (и будет лучшим соответствием) и U const& для постоянных значений (и быть неоднозначным). Насколько мне известно, не существует правила частичного упорядочения для устранения этой второй неоднозначности. Однако этот уже известен .

-- Редактировать --

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

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

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

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

-- Редактировать Конец --


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

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

Другая ситуация, если вы сделаете это:

SomeComplexObject o;
function2(move(o));

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

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

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

4 голосов
/ 05 апреля 2010

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

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

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

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

2 голосов
/ 05 апреля 2010

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


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

Я не могу помочь тебе с отдыхом, прости.

...