Страницы

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

пятница, 20 декабря 2019 г.

std::forward в нешаблонной функции

#cpp #cpp11


Есть функция, принимающая rvalue ссылку и передающая её другой функции, принимающей
rvalue ссылку:

void inner(int&& a)
{/* ... */}

void outer(int&& a)
{
    inner(std::move(a));
}


Я написал std::move, потому что для передачи rvalue ссылок мы вроде как используем
std::move. Но есть подозрение, что в данном случае будет вызвана лишняя операция перемещения
и для её исключения, возможно, стоит вызвать std::forward, чтобы передать ссылку напрямую,
а не создавать временные объекты и перемещать содержимое между ними:

void outer(int&& a)
{
    inner(std::forward(a));
}


Подскажите, пожалуйста, какой вариант правильней?
    


Ответы

Ответ 1



Вообще-то, ни в первом, ни во втором случае сами move и forward ни к каким действиям не приводят, и никаких лишних перемещений не вызывают. Как по мне, правильнее и логичнее применение move - потому что у вас четко передается rvalue, а не универсальная ссылка, которая может быть и l, и r (в случае шаблонной функции) - и когда нужно просто передать то, что было получено, т.е. lvalue оставить lvalue, а rvalue - rvalue. Это не ваш случай. По-моему, так. (с) Пух

Ответ 2



std::forward требует явного указания шаблонного аргумента и имеет смысл использовать только в контексте шаблонов в универсальными ссылками, которые могут разворачиваться как в rvalue, так и в lvalue. То, что вы написали, а именно std::forward(a) просто напросто не скомпилируется. Т.к. ваши функции принимают rvalue ссылки, то std::move тут единственный правильный выбор. С тем же эффектом можно было бы написать inner(std::forward(a)), но это длиннее.

Ответ 3



Ни std::move, ни std::forward никогда ничего не перемещают. Они только изменяют квалификаторы ссылочности. Для int это вообще не актуально, но если у вас есть класс c конструктором перемещения и копирования, эти квалификаторы ссылочности позволяют выбрать нужный конструктор. Изменение квалификаторов происходит так: #include #include struct Foo{ int v = 0; Foo() = default; Foo(int v_): v{v_} {} Foo(const Foo&) = default; Foo(Foo&&) = default; }; void test(){ Foo f1; Foo& ref_foo = f1; const Foo& const_ref_foo = ref_foo; static_assert(std::is_same< Foo&& , decltype(std::move(ref_foo))>{}, ""); static_assert(std::is_same< const Foo&&, decltype(std::move(const_ref_foo))>{}, ""); static_assert( std::is_convertible< const Foo&&, const Foo&>{}, ""); // const Foo&& может вызвать конструктор копирования static_assert( ! std::is_convertible< const Foo&&, Foo&&>{}, ""); // const Foo&& не может вызвать конструктор перемещения static_assert(std::is_same< Foo&& , decltype(std::forward(f1))>{}, ""); // Вызовет конструктор перемещения static_assert(std::is_same< Foo& , decltype(std::forward(ref_foo))>{}, ""); // Вызовет конструктор копирования static_assert(std::is_same< const Foo&&, decltype(std::forward(const_ref_foo))>{}, ""); // Вызовет конструктор копирования } Т.е. в случае void inner(Foo&& a){ } void outer(Foo&& a){ inner(std::move(a)); } При вызове inner ни копирования, ни перемещения не произойдет, будет просто передана ссылка. А вот при таком использовании: void inner(Foo&& a){ static Foo v = std::move(a); } void outer(Foo&& a){ inner(std::move(a)); } При вызове outer произойдет ровно одно перемещение, причем в теле inner, а не outer.

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

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