| Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате |
| Форум программистов > C/C++: Общие вопросы > c++ x11 и rvalue reference |
| Автор: xTr1m 25.9.2013, 17:44 | ||||
День добрый. Просматривая стандарт, увидел такую классную штуку как rvalue reference. Захотел применить, допустим, есть код
то есть проблема в том, что мы выделяем память память под большую строку, потом возвращаем ее из функции, но при этом вызывается конструктор копирования, который снова выделяет память и удаляет старую. Как я понимаю, тут можно использовать механику конструктора перемещения, скорее всего, используя rvalue. Но у меня не получилось. Пробовал так
ну и еще несколькими дурацкими способами, но не получилось. Конструктор копирования все равно вызывается. Может я что-то не так понял про r-value ref? Спасибо. |
| Автор: vinter 25.9.2013, 18:04 | ||
| Покажите CString. У него есть конструктор CString(CString&& rhs)? Если да, то какой компилятор? Добавлено через 1 минуту и 13 секунд
Знаете правило: никогда не брать не-константную ссылку на объект возвращенный из функции? rvalue reference в этом смысле ничем не отличается. Так что так писать весьма вредно. |
| Автор: azesmcar 25.9.2013, 18:11 | ||
используй std::move
|
| Автор: baldina 25.9.2013, 18:17 |
| xTr1m, ничего вы таким образом не выиграете, даже наоборот. в вашем случае будет NRVO работать. rvalue reference хорошо применять там, где допускается семантика переноса, не очевидная компилятору (т.е. везде, где не идет речь о создании и возврате временного объекта). |
| Автор: xTr1m 25.9.2013, 18:24 |
| По поводу правила никогда не брать не-константную ссылку на объект возвращенный из функции это я понимаю, но как я понял rvalue как раз для этого, нет? То есть, был некий объект в функции и мы хотим его вернуть. Понятно, что возвращать обычную ссылку нельзя, а rvalue не для этого был создан? То есть мы переносим объект из функции в другой объект без копирования, а переносом (указателя или что там еще). CString - это из MFC и там конечно же нет такого. Делать обертку к нему? Не знаю насколько это будет целесообразно. Может посмотрю в сторону std::string, может там конструктор перемещения определен. За пример с std::move спасибо. Интересно, а можно использовать его с CString? Но это опять же обертку делать. А вообще как решаются такие задачи. Задача: в функции генерируется большая строка, хочу вернуть ее без перевыделения памяти. |
| Автор: vinter 25.9.2013, 18:25 |
| azesmcar, убери && после C. Твой код конструктор перемещения не вызывает, а лишь создаёт rvalue ссылку. |
| Автор: azesmcar 25.9.2013, 18:26 |
| А вообще можно реализовать Copy-on-write. |
| Автор: xTr1m 25.9.2013, 18:29 | ||
Сейчас попробовал сделать так
и тут сразу вызывается конструктор перемещения. попробую засечь насколько быстрее получается. |
| Автор: vinter 25.9.2013, 18:31 | ||||||
Не совсем. Rvalue reference никогда нельзя возвращать из функции. Это моё утверждение не совсем верно, но пока полностью не проникнитесь rvalue ссылками лучше думать именно так. Т.к. возврщать ссылку на rvalue нужно в очень ограниченном числе случаев. Возвращать нужно просто по значению. Для использования семантики перемещений в возвращаемом объекте код менять не нужно, нужно просто добавить конструктор перемещения в тот класс, перемещения которго Вы желаете.
Тем не менее это единственный выход. Другого нет.
Да, все библиотечные классы имеют конструктор перемещения(для которых целесообразно, по крайней мере) Я писал http://scrutator.me/post/2011/08/02/rvalue-refs.aspx про rvalue references. Посмотрите, может проясните что-нибудь для себя. |
| Автор: xTr1m 25.9.2013, 18:32 |
| Большое спасибо, прочту. |
| Автор: xTr1m 25.9.2013, 19:05 | ||||
Очень странно, написал две функции
и прогнал так
и время выполнения в release одинаково! Хотя в одном случае идет копирование (mfc CString), а в другом перемещение (std::string). Тогда в чем выигрыш в семантике перемещения? |
| Автор: vinter 25.9.2013, 19:17 |
| как выше указал(а) baldina работает NRVO, т.е. ни конструктор копирования, ни конструктор перемещения не вызываются вообще. NRVO быстрее и, соответственно, срабатывает когда можно. Конструктор перемещения нужен для всех остальных случаев. А может весь код вообще выброшен компилятором как бесполезный. Всё зависит от агрессивности оптимизатора |
| Автор: xTr1m 25.9.2013, 19:33 |
| То есть NRVO применятеся только в релизе? (спрашиваю, так как в дебаге смотрел и вызывались конструктор копирования и перемещения) Добавлено через 3 минуты и 16 секунд То есть получается, что я так ничего несоптимизирую (имею в виду возврат временного объекта из функции). А можно какой-нибудь пример, где применяется семантика перемещения без временного объекта. Я пока читал про std::thread понял, что для его передачи между функциями, например, используется как раз std::move. А где еще? |
| Автор: baldina 25.9.2013, 20:14 | ||
да, потому что в debug отключена оптимизация
|
| Автор: vinter 25.9.2013, 20:14 | ||
NRVO это дело оптимизатора. Оптимизация, как правило, в дебаге выключена. RVO и NRVO не всегда применимы. Тогда идёт выигрыш от перемещения. std::unique_ptr еще без перемещения не работает. Применяется везде где нужно, где есть объект который можно переместить и там где есть объект который только и можно перемещать. Многие отказываются от передачи по константной ссылке аргументов функции в пользу аргументов по значению, когда передаются типы с перемещающим конструктором. Вариантов масса. Просто искусственно не нужно использовать, я считаю. Ну и взять в привычку добавлять конструктор перемещения для всех своих классов у которых есть явный конструктор копирования. |
| Автор: baldina 25.9.2013, 20:19 | ||
кстати, можно просто
|
| Автор: vinter 25.9.2013, 20:22 |
| baldina, нельзя, он у тебя просто буфер скопирует побитово и, в результате, будет утечка. Да и std::move в твоём ответе лишние(избыточные) явно. |
| Автор: baldina 25.9.2013, 20:24 |
| тут посмотри http://en.cppreference.com/w/cpp/language/move_constructor Добавлено через 44 секунды vinter, он побитово скопирует указатель. где утечка? |
| Автор: vinter 25.9.2013, 20:26 |
| предыдущий указатель не удалится. А новый, кстати, будет удалён xvalue(тот с которого переместили внутренности) объектом. Так что не просто утечка, а утечка с невалидным указателем в новом объекте. Первый твой ответ был правильным. Добавлено через 5 минут и 31 секунду Про утечку я неправильно сказал, это же конструктор. Но вопрос с невалидным указателем остается. |
| Автор: xTr1m 25.9.2013, 20:34 |
| Всем большое спасибо. Пойду еще статью прочитаю, чтобы точно все понять =)) А пока понял, что не нужно применять что бы просто применить (пусть и очень хочется =)) То есть использование достаточно специфическое, и в повседневном коде можно обойтись стандартными инструментами. |
| Автор: akizelokro 25.9.2013, 20:44 | ||
А так не пробовал?
Добавлено @ 20:44 Да можно применять. Если с CString прокатит, конечно. |
| Автор: xTr1m 25.9.2013, 20:46 |
| Сейчас попробовал. По времени также, как если бы без константной ссылки |
| Автор: baldina 25.9.2013, 21:12 |
почему он невалидный? если не использовать явный перенос (т.е. конструктор применяется только к временным объектам), то все в порядке http://ideone.com/DkFQbw если использовать явный перенос, надо предотвратить повторное освобождение памяти в деструкторе http://ideone.com/D4m7KI ты об этом? |
| Автор: vinter 26.9.2013, 08:05 | ||||
baldina,
Там не всё в порядке. Ты в этом коде так и не вызвал конструктор перемещения. Добавь в тот код еще одну строчку(String c(std::move(a));) и ты увидишь, что указатель удалится 2 раза. Что уже является UB. Но в общем случае до второго удаления есть еще пользование этим объектом, у которого, на деле, указатель уже невалиден. Мне кажется ты не совсем понимаешь всю это чехарду с перемещением. std::move и конструктор перемещения никогда и ничего не перемещают, если только ты не делаешь этого явно. Чем, по сути, отличается конструктор копирования от конструктора перемещения по умолчанию? первый делает a = rhs.a, а второй a = std::move(rhs.a);. Для композитных объектов класса у которых нет конструктора перемещения поведение обоих конструкторов по умолчанию идентично. Т.е. в твоём случае это наличие двух объектов, которые ссылаются на один буфер, что есть заезженная проблема из старого доброго C++ к которой прибегают всегда, когда объясняют зачем писать пользовательский конструктор копирования. Замени char* на std::string и всё будет работать с конструктором перемещения по умолчанию, т.к. std::string "знает" как работать с перемещением. Твой второй пример есть пример правильного написания конструктора перемещения. Добавлено через 3 минуты и 34 секунды akizelokro, так писать нельзя, ты возвращаешь ссылку на объект, которого больше нет. Должно быть так:
В этом случае время жизни временного объекта продлевается на всё время жизни константной ссылки. Это справедливо только для константных ссылок. |
| Автор: baldina 26.9.2013, 08:22 |
| vinter, так я об этом и написал, только букф поменьше... |
| Автор: vinter 26.9.2013, 08:33 |
| Да нет же. Твой код с String (String&&) = default; неверен. И я объяснил почему. |
| Автор: azesmcar 26.9.2013, 10:06 | ||
ага, спасибо. |