Предположим есть функции foo, которая принимает std::unique_ptr во владение. Хотелось бы услышать мнение уважаемого комьюнити по поводу того, как более верно эта функция должна выглядеть. У меня есть 2 варианта:
Здравствуйте, niXman, Вы писали:
X>первый — более универсальный.
чем это? учитывая что у unique_ptr нет копирующего конструктора, первый вариант попросту не возможен!
X>второй — более тюнингованный
собственно он же и единственный. для передачи владения нужно явно делать std::move(ptr).
Здравствуйте, zaufi, Вы писали:
Z>чем это? учитывая что у unique_ptr нет копирующего конструктора, первый вариант попросту не возможен!
упс, не обратил внимание что это unique. сорри.
пачка бумаги А4 стОит 2000 р, в ней 500 листов. получается, лист обычной бумаги стОит дороже имперского рубля =)
Здравствуйте, zaufi, Вы писали:
Z>Здравствуйте, niXman, Вы писали:
X>>первый — более универсальный. Z>чем это? учитывая что у unique_ptr нет копирующего конструктора, первый вариант попросту не возможен!
X>>второй — более тюнингованный Z>собственно он же и единственный. для передачи владения нужно явно делать std::move(ptr).
Первый вариант будет работать если фактическим параметром будет rvalue-выражение. Другими словами, первый вариант будет работать в тех же случаях, что и второй
При использовании оптимизирующего компилятора, думаю, разницы между этими двумя вариантами нет. А для неоптимизирующего, очевидно, второй вариант предпочтительней.
--
Справедливость выше закона. А человечность выше справедливости.
Здравствуйте, rg45, Вы писали:
X>>>второй — более тюнингованный Z>>собственно он же и единственный. для передачи владения нужно явно делать std::move(ptr).
R>Первый вариант будет работать если фактическим параметром будет rvalue-выражение. Другими словами, первый вариант будет работать в тех же случаях, что и второй
ну я специально не стал заострять на этом внимание чтобы у ТС не было желания так делать, ибо можно внезапно огрести, если не до конца понимать суть происходящего
да и в целом, ценность такого кода стремительно стремится к нулю -- заводить unique_ptr только для того чтобы тутже передать владение вызываемой функции... мне одному кажется это странным/нелепым?
Здравствуйте, Аноним, Вы писали:
А>Какой из них более правильный?
http://liveworkspace.org/code/3Mun0g$0
отсюда видно, что при использовании семантики перемещения поведение меняется в зависимости от
содержимого функции. Поэтому я считаю, что первый вариант лучше.
Здравствуйте, rumia, Вы писали:
R>Здравствуйте, Аноним, Вы писали:
А>>Какой из них более правильный?
R>http://liveworkspace.org/code/3Mun0g$0 R>отсюда видно, что при использовании семантики перемещения поведение меняется в зависимости от R>содержимого функции. Поэтому я считаю, что первый вариант лучше.
и что показывает данный пример?? а ничего! только лишь что компиляторы нынче несколько умнее человеков...
как видно в примере, все что попадает в эти funcN вообще не используются! this участвующих объектов всегда тот, который был в main: т.е. никакой "передачи владения" в данном _тривиальном_ случае вообще не происходит. даже симуляция каких-то действий с переданным объектом (вызов foo()) не заставляет компилятор генерить бессмысленный код (вызывать move конструкторы), даже на -О0...
Здравствуйте, zaufi, Вы писали:
Z>Здравствуйте, rumia, Вы писали:
R>>Здравствуйте, Аноним, Вы писали:
А>>>Какой из них более правильный?
R>>http://liveworkspace.org/code/3Mun0g$0 R>>отсюда видно, что при использовании семантики перемещения поведение меняется в зависимости от R>>содержимого функции. Поэтому я считаю, что первый вариант лучше.
Z>и что показывает данный пример?? а ничего! только лишь что компиляторы нынче несколько умнее человеков... Z>как видно в примере, все что попадает в эти funcN вообще не используются! this участвующих объектов всегда тот, который был в main: т.е. никакой "передачи владения" в данном _тривиальном_ случае вообще не происходит. даже симуляция каких-то действий с переданным объектом (вызов foo()) не заставляет компилятор генерить бессмысленный код (вызывать move конструкторы), даже на -О0...
кстати, как можно видеть, добавления "симуляции использования" переданного объекта, придает твоему изначальному примеру "ожидаемое" поведение ("p3" таки не печатается)
Здравствуйте, zaufi, Вы писали:
Z>как видно в примере, все что попадает в эти funcN вообще не используются! this участвующих объектов всегда тот, который был в main
oops, это я перегнул -- this это у экземпляра test... он "не при делах"
anyway, никакие move конструкторы не вызываются...
Здравствуйте, zaufi, Вы писали:
Z>Здравствуйте, zaufi, Вы писали:
Z>>как видно в примере, все что попадает в эти funcN вообще не используются! this участвующих объектов всегда тот, который был в main Z>oops, это я перегнул -- this это у экземпляра test... он "не при делах"
Z>anyway, никакие move конструкторы не вызываются...
OOPS2 )) чорт, да и не должны ) -- это же у unique_ptr move конструкторы должны вызываться
но все равно: как только есть какое-то реальное использование передаваемого unique_ptr пример работает как полагается... "p3" таки нет на экране
Здравствуйте, rumia, Вы писали:
R>http://liveworkspace.org/code/3Mun0g$0 R>отсюда видно, что при использовании семантики перемещения поведение меняется в зависимости от R>содержимого функции. Поэтому я считаю, что первый вариант лучше.
ИМХО, не криминал. Ни использование std::move, ни передача rvalue reference в функцию еще не означают, что перемещение обязано состояться. Даже одна и та же функция может вести себя по-разному в зависимости от обстоятельств. Такова уж природа rvalue references, нужно просто это воспринимать как норму. Тем более, что для тех объектов, для которых разрешены и копирование и перемещение, использование rvalue references может оказаться полезной защитой от непреднамеренного копирования. Да и перемещение тоже — оно ведь в отдельных случаях может оказаться не таким уж дешевым. Зачем же выполнять перемещение, когда это не является необходимым?
--
Справедливость выше закона. А человечность выше справедливости.
Re[2]: Передача владения: вот как все на самом деле...
как видим, последний case выглядит какбудто в func3 передали ссылку на test (который лежит в стеке main'a) -- и на самом деле так оно и есть!
как известно функция std::move ничего не делает -- т.е. она вообще не занимается ни каким перемещением!
фактически она делает только вот такой вот cast: static_cast<X&&>(x) -- о чем можно почитать (здесь или здесь, ну или повтыкать исходники ) все это только лишь для того, чтобы выбрать нужную функцию в случае с перегрузкой. хороший пример демонстрирующий это есть здесь -- ниже выделенного жирным
The compiler treats a named rvalue reference as an lvalue and an unnamed rvalue reference as an rvalue.
ключем к пониманию происходящего можно выделить вот это (отсюда)
An rvalue reference behaves just like an lvalue reference except that it can bind to a temporary (an rvalue), whereas you can not bind a (non const) lvalue reference to an rvalue.
т.е. в нашем последнем примере, тот факт, что func3 принимает rvalue еше ничего не значит -- т.е. это вовсе не означает, что в этом месте должна происходить передача владения (особенно помятуя, о том, что std::move ничего не делает!). внутри func3 с полученной ссылкой, тоже ничего такого не делается -- т.е. конструктор перемещения из параметра кудато еще не вызывается -- таким образом, в этом вызове мы всего лишь имеем ссылку на некий экземпляр test (лежащий в стеке у main)... и ничего с ним не делаем, чтобы приводило к перемещению его содержимого... ну собственно а куда?
в первом и во втором случае, std:move позволяет всего лишь выбрать нужный конструктор (ага, он называется move constcutctor) -- собственно он и делает всю работу, никак не std::move -- поэтому ожидать от третего примера что p3 сделается "невалидным" не верно! -- он никуда и не перемещался!
Re[3]: Передача владения: вот как все на самом деле...
Здравствуйте, zaufi, Вы писали:
Z>т.е. в нашем последнем примере, тот факт, что func3 принимает rvalue еше ничего не значит -- т.е. это вовсе не означает, что в этом месте должна происходить передача владения (особенно помятуя, о том, что std::move ничего не делает!). Z>...
+1
И поэтому имя 'move' лично мне кажется не очень удачным для этой функции.
--
Справедливость выше закона. А человечность выше справедливости.
Здравствуйте, rg45, Вы писали:
R>Здравствуйте, rumia, Вы писали:
R>>http://liveworkspace.org/code/3Mun0g$0 R>>отсюда видно, что при использовании семантики перемещения поведение меняется в зависимости от R>>содержимого функции. Поэтому я считаю, что первый вариант лучше.
R>ИМХО, не криминал. Ни использование std::move, ни передача rvalue reference в функцию еще не означают, что перемещение обязано состояться. Даже одна и та же функция может вести себя по-разному в зависимости от обстоятельств. Такова уж природа rvalue references, нужно просто это воспринимать как норму. Тем более, что для тех объектов, для которых разрешены и копирование и перемещение, использование rvalue references может оказаться полезной защитой от непреднамеренного копирования. Да и перемещение тоже — оно ведь в отдельных случаях может оказаться не таким уж дешевым. Зачем же выполнять перемещение, когда это не является необходимым?
К тому же, если рассматривать более общий случай, когда для объекта разрешено и копирование и перемещение, вполне разумно будет сделать две перегрузки — одну для обычной константной ссылки и другую для rvalue-ссылки. Например, если бы вместо unique_ptr был shared_ptr, то одна перегрузка прекрасно бы реализовывалась через другую:
void foo(shared_ptr<Bar>&& p)
{
/*"Рабочая лошадка"*/
// . . .
}
void foo(const shared_ptr<Bar>& p)
{
foo(shared_ptr<Bar>(p)); // Создаем временную копию умного указателя
// и передаем ее по rvalue-ссылке в первую функцию
}
--
Справедливость выше закона. А человечность выше справедливости.
в этом и есть проблема : непредсказуемость поведения или по-другому: слишком много вариантов поведения
можно провести аналогию: передача по неконст ссылке или по значению\конст ссылке
void f(int i)
{
...
}
void g(int& x)
{
...
}
как бы функция g не обязана изменить аргумент, однако может
f не может изменить и это некоторые гарантии для пользователя этой функции
именно поэтому предпочитаю передавать по значению или по неконстантной ссылке
меньше свободы — больше предсказуемости и меньше давление на мозг
по поводу move: мне кажется, что все же если ограничить использование move до тех случаев, когда реально надо мувить данные, то код понимать и поддерживать будет проще
а что до оверхедов передвигания смартпойнтеров не 1 раз, а два, то я спокоен. проседание у меня будет наверняка в других местах да и профайлер мне поможет
и да, std::move неудачное название, но я под него подстраиваюсь
Здравствуйте, uzhas, Вы писали:
U>Здравствуйте, rg45, Вы писали:
R>> еще не означают
U>в этом и есть проблема : непредсказуемость поведения или по-другому: слишком много вариантов поведения U>можно провести аналогию: передача по неконст ссылке или по значению\конст ссылке U>[...] U>как бы функция g не обязана изменить аргумент, однако может U>f не может изменить и это некоторые гарантии для пользователя этой функции U>именно поэтому предпочитаю передавать по значению или по неконстантной ссылке U>меньше свободы — больше предсказуемости и меньше давление на мозг
U>по поводу move: мне кажется, что все же если ограничить использование move до тех случаев, когда реально надо мувить данные, то код понимать и поддерживать будет проще U>а что до оверхедов передвигания смартпойнтеров не 1 раз, а два, то я спокоен. проседание у меня будет наверняка в других местах да и профайлер мне поможет U>и да, std::move неудачное название, но я под него подстраиваюсь
Возможно, я ошибаюсь, но у меня такая предубежденность, что при идеальных архитектуре и дизайне move не нужен вовсе. Т.о. move — это просто костыль, частота использования которого может служить мерилом плохого дизайна.
На счет свободы и предсказуемости — снова приходим к вопросу о том, какой случай считать частным, какой общим. Я вполне допускаю, что в каких-то случаях есть резон перестраховаться и уменьшить число возможных вариантов поведения. Но я бы не торопился с обобщениями, и не стал бы утверждать, что так следует поступать всегда. Ведь рассуждая подобным образом достаточно скоро можно прийти к заключению, что передача параметров по ссылке — это зло, а языки, в которых нет ни ссылок ни указателей — единственно верный путь.
--
Справедливость выше закона. А человечность выше справедливости.
Здравствуйте, rg45, Вы писали:
R>Возможно, я ошибаюсь, но у меня такая предубежденность, что при идеальных архитектуре и дизайне move не нужен вовсе. Т.о. move — это просто костыль, частота использования которого может служить мерилом плохого дизайна.
Здравствуйте, Evgeny.Panasyuk, Вы писали:
EP>Здравствуйте, rg45, Вы писали:
R>>Возможно, я ошибаюсь, но у меня такая предубежденность, что при идеальных архитектуре и дизайне move не нужен вовсе. Т.о. move — это просто костыль, частота использования которого может служить мерилом плохого дизайна.
EP>А как же алгоритмы, например быстрый rotate? EP>А как же C++11 std::swap в конце концов? EP>
EP>template <class T> void swap (T& a, T& b)
EP>{
EP> T c(std::move(a)); a=std::move(b); b=std::move(c);
EP>}
EP>
swap можно можно реализовать при помощи std::forward:
#include <iostream>
#include <utility>
template <class T>
void swap (T& a, T& b)
{
T c(std::forward<T>(a)); a=std::forward<T>(b); b=std::forward<T>(c);
}
int main(int argc, char **argv)
{
int a = 1, b = 2;
swap(a, b);
std::cout << a << ", " << b << std::endl;
}
--
Справедливость выше закона. А человечность выше справедливости.