std::unique_ptr. Передача владения.
От: Аноним  
Дата: 13.03.13 13:35
Оценка:
Все привет!

Предположим есть функции foo, которая принимает std::unique_ptr во владение. Хотелось бы услышать мнение уважаемого комьюнити по поводу того, как более верно эта функция должна выглядеть. У меня есть 2 варианта:

Вариант номер 1:

template<class T>
void foo(std::unique_ptr<T> ptr);


Вариант номер 2:

template<class T>
void foo(std::unique_ptr<T>&& ptr); /*as "rvalue reference"*/


Какой из них более правильный?
Re: std::unique_ptr. Передача владения.
От: niXman Ниоткуда https://github.com/niXman
Дата: 13.03.13 13:38
Оценка:
первый — более универсальный.
второй — более тюнингованный
пачка бумаги А4 стОит 2000 р, в ней 500 листов. получается, лист обычной бумаги стОит дороже имперского рубля =)
Re[2]: std::unique_ptr. Передача владения.
От: zaufi Земля  
Дата: 13.03.13 13:51
Оценка: -1
Здравствуйте, niXman, Вы писали:

X>первый — более универсальный.

чем это? учитывая что у unique_ptr нет копирующего конструктора, первый вариант попросту не возможен!

X>второй — более тюнингованный

собственно он же и единственный. для передачи владения нужно явно делать std::move(ptr).
Re[3]: std::unique_ptr. Передача владения.
От: niXman Ниоткуда https://github.com/niXman
Дата: 13.03.13 13:57
Оценка:
Здравствуйте, zaufi, Вы писали:

Z>чем это? учитывая что у unique_ptr нет копирующего конструктора, первый вариант попросту не возможен!

упс, не обратил внимание что это unique. сорри.
пачка бумаги А4 стОит 2000 р, в ней 500 листов. получается, лист обычной бумаги стОит дороже имперского рубля =)
Re[3]: std::unique_ptr. Передача владения.
От: rg45 СССР  
Дата: 13.03.13 14:10
Оценка:
Здравствуйте, zaufi, Вы писали:

Z>Здравствуйте, niXman, Вы писали:


X>>первый — более универсальный.

Z>чем это? учитывая что у unique_ptr нет копирующего конструктора, первый вариант попросту не возможен!

X>>второй — более тюнингованный

Z>собственно он же и единственный. для передачи владения нужно явно делать std::move(ptr).

Первый вариант будет работать если фактическим параметром будет rvalue-выражение. Другими словами, первый вариант будет работать в тех же случаях, что и второй

При использовании оптимизирующего компилятора, думаю, разницы между этими двумя вариантами нет. А для неоптимизирующего, очевидно, второй вариант предпочтительней.
--
Справедливость выше закона. А человечность выше справедливости.
Re[4]: std::unique_ptr. Передача владения.
От: zaufi Земля  
Дата: 13.03.13 14:40
Оценка: +1
Здравствуйте, rg45, Вы писали:

X>>>второй — более тюнингованный

Z>>собственно он же и единственный. для передачи владения нужно явно делать std::move(ptr).

R>Первый вариант будет работать если фактическим параметром будет rvalue-выражение. Другими словами, первый вариант будет работать в тех же случаях, что и второй


ну я специально не стал заострять на этом внимание чтобы у ТС не было желания так делать, ибо можно внезапно огрести, если не до конца понимать суть происходящего

да и в целом, ценность такого кода стремительно стремится к нулю -- заводить unique_ptr только для того чтобы тутже передать владение вызываемой функции... мне одному кажется это странным/нелепым?
Re: std::unique_ptr. Передача владения.
От: rumia Россия  
Дата: 13.03.13 14:52
Оценка: 15 (1)
Здравствуйте, Аноним, Вы писали:

А>Какой из них более правильный?


http://liveworkspace.org/code/3Mun0g$0
отсюда видно, что при использовании семантики перемещения поведение меняется в зависимости от
содержимого функции. Поэтому я считаю, что первый вариант лучше.
Re[2]: std::unique_ptr. Передача владения.
От: zaufi Земля  
Дата: 13.03.13 15:53
Оценка:
Здравствуйте, rumia, Вы писали:

R>Здравствуйте, Аноним, Вы писали:


А>>Какой из них более правильный?


R>http://liveworkspace.org/code/3Mun0g$0

R>отсюда видно, что при использовании семантики перемещения поведение меняется в зависимости от
R>содержимого функции. Поэтому я считаю, что первый вариант лучше.

и что показывает данный пример?? а ничего! только лишь что компиляторы нынче несколько умнее человеков...
как видно в примере, все что попадает в эти funcN вообще не используются! this участвующих объектов всегда тот, который был в main: т.е. никакой "передачи владения" в данном _тривиальном_ случае вообще не происходит. даже симуляция каких-то действий с переданным объектом (вызов foo()) не заставляет компилятор генерить бессмысленный код (вызывать move конструкторы), даже на -О0...
Re[3]: std::unique_ptr. Передача владения.
От: zaufi Земля  
Дата: 13.03.13 15:56
Оценка:
Здравствуйте, zaufi, Вы писали:

Z>Здравствуйте, rumia, Вы писали:


R>>Здравствуйте, Аноним, Вы писали:


А>>>Какой из них более правильный?


R>>http://liveworkspace.org/code/3Mun0g$0

R>>отсюда видно, что при использовании семантики перемещения поведение меняется в зависимости от
R>>содержимого функции. Поэтому я считаю, что первый вариант лучше.

Z>и что показывает данный пример?? а ничего! только лишь что компиляторы нынче несколько умнее человеков...

Z>как видно в примере, все что попадает в эти funcN вообще не используются! this участвующих объектов всегда тот, который был в main: т.е. никакой "передачи владения" в данном _тривиальном_ случае вообще не происходит. даже симуляция каких-то действий с переданным объектом (вызов foo()) не заставляет компилятор генерить бессмысленный код (вызывать move конструкторы), даже на -О0...

кстати, как можно видеть, добавления "симуляции использования" переданного объекта, придает твоему изначальному примеру "ожидаемое" поведение ("p3" таки не печатается)
Re[3]: std::unique_ptr. Передача владения.
От: zaufi Земля  
Дата: 13.03.13 16:00
Оценка:
Здравствуйте, zaufi, Вы писали:

Z>как видно в примере, все что попадает в эти funcN вообще не используются! this участвующих объектов всегда тот, который был в main

oops, это я перегнул -- this это у экземпляра test... он "не при делах"

anyway, никакие move конструкторы не вызываются...
Re[4]: std::unique_ptr. Передача владения.
От: zaufi Земля  
Дата: 13.03.13 16:03
Оценка:
Здравствуйте, zaufi, Вы писали:

Z>Здравствуйте, zaufi, Вы писали:


Z>>как видно в примере, все что попадает в эти funcN вообще не используются! this участвующих объектов всегда тот, который был в main

Z>oops, это я перегнул -- this это у экземпляра test... он "не при делах"

Z>anyway, никакие move конструкторы не вызываются...


OOPS2 )) чорт, да и не должны ) -- это же у unique_ptr move конструкторы должны вызываться

но все равно: как только есть какое-то реальное использование передаваемого unique_ptr пример работает как полагается... "p3" таки нет на экране
Re[5]: std::unique_ptr. Передача владения.
От: rumia Россия  
Дата: 13.03.13 16:28
Оценка:
Здравствуйте, zaufi, Вы писали:

Z>"p3" таки нет на экране


http://liveworkspace.org/code/9biAN$0
есть. Вы func1 два раза написали.
Re[2]: std::unique_ptr. Передача владения.
От: rg45 СССР  
Дата: 13.03.13 20:21
Оценка:
Здравствуйте, rumia, Вы писали:

R>http://liveworkspace.org/code/3Mun0g$0

R>отсюда видно, что при использовании семантики перемещения поведение меняется в зависимости от
R>содержимого функции. Поэтому я считаю, что первый вариант лучше.

ИМХО, не криминал. Ни использование std::move, ни передача rvalue reference в функцию еще не означают, что перемещение обязано состояться. Даже одна и та же функция может вести себя по-разному в зависимости от обстоятельств. Такова уж природа rvalue references, нужно просто это воспринимать как норму. Тем более, что для тех объектов, для которых разрешены и копирование и перемещение, использование rvalue references может оказаться полезной защитой от непреднамеренного копирования. Да и перемещение тоже — оно ведь в отдельных случаях может оказаться не таким уж дешевым. Зачем же выполнять перемещение, когда это не является необходимым?
--
Справедливость выше закона. А человечность выше справедливости.
Re[2]: Передача владения: вот как все на самом деле...
От: zaufi Земля  
Дата: 13.03.13 21:48
Оценка: 2 (1) +1
до мня вот дошло наконец, почему, последний пример кажется "неправильным"... а на самом деле, все так и должно быть!

http://liveworkspace.org/code/30T2cT$0
здесь пример в котором unique_ptr заменен на test -- структуру, которая спамит все операции над собой.

как видим, последний 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]: Передача владения: вот как все на самом деле...
От: rg45 СССР  
Дата: 13.03.13 22:17
Оценка: +1
Здравствуйте, zaufi, Вы писали:

Z>т.е. в нашем последнем примере, тот факт, что func3 принимает rvalue еше ничего не значит -- т.е. это вовсе не означает, что в этом месте должна происходить передача владения (особенно помятуя, о том, что std::move ничего не делает!).

Z>...

+1

И поэтому имя 'move' лично мне кажется не очень удачным для этой функции.
--
Справедливость выше закона. А человечность выше справедливости.
Re[3]: std::unique_ptr. Передача владения.
От: rg45 СССР  
Дата: 13.03.13 22:42
Оценка: -1
Здравствуйте, 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-ссылке в первую функцию
}
--
Справедливость выше закона. А человечность выше справедливости.
Re[3]: std::unique_ptr. Передача владения.
От: uzhas Ниоткуда  
Дата: 14.03.13 11:07
Оценка:
Здравствуйте, rg45, Вы писали:

R> еще не означают


в этом и есть проблема : непредсказуемость поведения или по-другому: слишком много вариантов поведения
можно провести аналогию: передача по неконст ссылке или по значению\конст ссылке
void f(int i)
{
...
}

void g(int& x)
{
...
}

как бы функция g не обязана изменить аргумент, однако может
f не может изменить и это некоторые гарантии для пользователя этой функции
именно поэтому предпочитаю передавать по значению или по неконстантной ссылке
меньше свободы — больше предсказуемости и меньше давление на мозг

по поводу move: мне кажется, что все же если ограничить использование move до тех случаев, когда реально надо мувить данные, то код понимать и поддерживать будет проще
а что до оверхедов передвигания смартпойнтеров не 1 раз, а два, то я спокоен. проседание у меня будет наверняка в других местах да и профайлер мне поможет
и да, std::move неудачное название, но я под него подстраиваюсь
Re[4]: std::unique_ptr. Передача владения.
От: rg45 СССР  
Дата: 14.03.13 12:35
Оценка: -1
Здравствуйте, uzhas, Вы писали:

U>Здравствуйте, rg45, Вы писали:


R>> еще не означают


U>в этом и есть проблема : непредсказуемость поведения или по-другому: слишком много вариантов поведения

U>можно провести аналогию: передача по неконст ссылке или по значению\конст ссылке
U>[...]
U>как бы функция g не обязана изменить аргумент, однако может
U>f не может изменить и это некоторые гарантии для пользователя этой функции
U>именно поэтому предпочитаю передавать по значению или по неконстантной ссылке
U>меньше свободы — больше предсказуемости и меньше давление на мозг

U>по поводу move: мне кажется, что все же если ограничить использование move до тех случаев, когда реально надо мувить данные, то код понимать и поддерживать будет проще

U>а что до оверхедов передвигания смартпойнтеров не 1 раз, а два, то я спокоен. проседание у меня будет наверняка в других местах да и профайлер мне поможет
U>и да, std::move неудачное название, но я под него подстраиваюсь

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

На счет свободы и предсказуемости — снова приходим к вопросу о том, какой случай считать частным, какой общим. Я вполне допускаю, что в каких-то случаях есть резон перестраховаться и уменьшить число возможных вариантов поведения. Но я бы не торопился с обобщениями, и не стал бы утверждать, что так следует поступать всегда. Ведь рассуждая подобным образом достаточно скоро можно прийти к заключению, что передача параметров по ссылке — это зло, а языки, в которых нет ни ссылок ни указателей — единственно верный путь.
--
Справедливость выше закона. А человечность выше справедливости.
Re[5]: std::unique_ptr. Передача владения.
От: Evgeny.Panasyuk Россия  
Дата: 14.03.13 13:10
Оценка:
Здравствуйте, rg45, Вы писали:

R>Возможно, я ошибаюсь, но у меня такая предубежденность, что при идеальных архитектуре и дизайне move не нужен вовсе. Т.о. move — это просто костыль, частота использования которого может служить мерилом плохого дизайна.


А как же алгоритмы, например быстрый rotate?
А как же C++11 std::swap в конце концов?
template <class T> void swap (T& a, T& b)
{
  T c(std::move(a)); a=std::move(b); b=std::move(c);
}
Re[6]: std::unique_ptr. Передача владения.
От: rg45 СССР  
Дата: 14.03.13 13:26
Оценка: -1
Здравствуйте, 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:

http://liveworkspace.org/code/3Mun0g$7

#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;
}
--
Справедливость выше закона. А человечность выше справедливости.
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.