Изучаю тут С++ для преподавания студентам.
И столкнулся с одной проблемой: не могу найти простого вменяемого примера для объяснения семантики переноса.
Теоретически все понятно: кода мы переносим файл, то сам файл с места не сдвигается, а меняется только управляющая информация.
Но в книгах:
Джоссатис. Стандартная библиотека С++11
Стенли Липпман (Лажойе, Му). Язык программирования С++.
Стивен Прата. Язык программирования С++. Лекции и упражнения.
Примеры несколько расплывчаты, как-то мутноваты.
Не могли бы наши гуру предложить простой (дубовый, кондовый... ), но в то же время точно отражающий семантику переноса пример?
Особенно обращаюсь к Кодту и Павлу Кузнецову — может быть вам уже приходилось это использовать и/или объяснять?
Буду весьма признателен.
Хочешь быть счастливым — будь им!
Без булдырабыз!!!
Здравствуйте, LaptevVV, Вы писали:
LVV>Не могли бы наши гуру предложить простой (дубовый, кондовый... ), но в то же время точно отражающий семантику переноса пример?
Скорее всего там не пример нужен.
Полагаю, сначала нужно разобраться с rvalue-ссылками, а затем понять как работает функция swap на их основе.
И вполне возможно, что студентам эту тему можно не рассказывать. И без этих знаний можно писать программы на С++.
Здравствуйте, anatoly1, Вы писали:
A>Скорее всего там не пример нужен. A>Полагаю, сначала нужно разобраться с rvalue-ссылками, а затем понять как работает функция swap на их основе. A>И вполне возможно, что студентам эту тему можно не рассказывать. И без этих знаний можно писать программы на С++.
Ну, можно и без виртуальности писать... Но ведь рассказываем жеж...
Хочешь быть счастливым — будь им!
Без булдырабыз!!!
Здравствуйте, jazzer, Вы писали:
LVV>>Не могли бы наши гуру предложить простой (дубовый, кондовый... ), но в то же время точно отражающий семантику переноса пример?
J>std::vector. Все понимали с первого слова.
Что вектор?
Правильно ли я тебя понимаю,
что можно рассматривать использование динамической памяти вектором с использованием unique_ptr — как некий аналог семантики переноса?
Хочешь быть счастливым — будь им!
Без булдырабыз!!!
Здравствуйте, LaptevVV, Вы писали:
LVV>Теоретически все понятно: кода мы переносим файл, то сам файл с места не сдвигается, а меняется только управляющая информация.
так как я изначально вопрос не понял, то сразу рождается совет: ни в коем случае не рассматривай move semantic на примере с файлами
возьми что-нибудь из мира C++, пусть будет std::vector. не усложняй и без того сложную тему
Здравствуйте, uzhas, Вы писали:
U>Здравствуйте, LaptevVV, Вы писали:
LVV>>Теоретически все понятно: кода мы переносим файл, то сам файл с места не сдвигается, а меняется только управляющая информация.
U>так как я изначально вопрос не понял, то сразу рождается совет: ни в коем случае не рассматривай move semantic на примере с файлами U>возьми что-нибудь из мира C++, пусть будет std::vector. не усложняй и без того сложную тему
Я и не рассматриваю.
Вот как раз про вектор — проясните, что тут имеется ввиду.
У стандартного вектора — семантика копирования.
В каких ситуациях может понадобиться семантика переноса?
Хочешь быть счастливым — будь им!
Без булдырабыз!!!
LVV>Вот как раз про вектор — проясните, что тут имеется ввиду. LVV>У стандартного вектора — семантика копирования. LVV>В каких ситуациях может понадобиться семантика переноса?
Здравствуйте, uzhas, Вы писали:
U>Здравствуйте, LaptevVV, Вы писали:
LVV>>Вот как раз про вектор — проясните, что тут имеется ввиду. LVV>>У стандартного вектора — семантика копирования. LVV>>В каких ситуациях может понадобиться семантика переноса?
U>какой у тебя вообще уровень понимания ?
Ну, например, что такое ленивые вычисления — я прекрасно понимаю. И могу реализовать.
Проблем при чтении Александреску — не испытывал... U>рекомендую начать с вики или SO: http://stackoverflow.com/questions/3106110/what-are-move-semantics
Спасибо, посмотрю.
Вопрос не в том, что я сам не понимаю. Вопрос придумать простой и понятный пример для объяснения студентам.
Вот с виртуальностью я долго мучился с примерами, пока не нашел пример у Шамиса в его книжке по С++Builder — очень просто и понятный...
И студенты с тех пор — тоже прекрасно СРАЗУ усваивают.
Вот и для семантики перехода хотелось бы придумать-поиметь аналогичный простой пример.
Хочешь быть счастливым — будь им!
Без булдырабыз!!!
Здравствуйте, LaptevVV, Вы писали:
LVV>Здравствуйте, jazzer, Вы писали:
LVV>>>Не могли бы наши гуру предложить простой (дубовый, кондовый... ), но в то же время точно отражающий семантику переноса пример?
J>>std::vector. Все понимали с первого слова. LVV>Что вектор? LVV>Правильно ли я тебя понимаю, LVV>что можно рассматривать использование динамической памяти вектором с использованием unique_ptr — как некий аналог семантики переноса?
Ну все знают, что вектор — это по сути просто указатель куда-то в кучу и красивый интерфейс к нему.
Поэтому рассматриваешь передачу вектора по значению в стандартном С++98 (через конструктор копирования по константной ссылке), обсуждаешь, почему такое не используется (потому что ненужная работа производится), а потом говоришь, что можно было бы передавать без потерь в скорости, если бы можно было просто перекинуть владение буфером в куче от одного объекта другому. Но что нормального способа это сделать в С++98 не было без специальных телодвижений (типа Boost.Move), потому что ты не знаешь, что тебе по константной ссылке пришло — объект, у которого можно все забрать или нормальный объект, который еще может использоваться где-то и должен оставаться нетронутым. А в С++11 есть rvalue-ссылки, которые автоматом распознают, когда такое можно делать, и просто перекидывают указатель.
А unique_ptr сам по себе такой же, просто у него вообще копирование запрещено.
Здравствуйте, jazzer, Вы писали:
LVV>>Правильно ли я тебя понимаю, LVV>>что можно рассматривать использование динамической памяти вектором с использованием unique_ptr — как некий аналог семантики переноса?
J>Ну все знают, что вектор — это по сути просто указатель куда-то в кучу и красивый интерфейс к нему. J>Поэтому рассматриваешь передачу вектора по значению в стандартном С++98 (через конструктор копирования по константной ссылке), обсуждаешь, почему такое не используется (потому что ненужная работа производится), а потом говоришь, что можно было бы передавать без потерь в скорости, если бы можно было просто перекинуть владение буфером в куче от одного объекта другому. Но что нормального способа это сделать в С++98 не было без специальных телодвижений (типа Boost.Move), потому что ты не знаешь, что тебе по константной ссылке пришло — объект, у которого можно все забрать или нормальный объект, который еще может использоваться где-то и должен оставаться нетронутым. А в С++11 есть rvalue-ссылки, которые автоматом распознают, когда такое можно делать, и просто перекидывают указатель.
J>А unique_ptr сам по себе такой же, просто у него вообще копирование запрещено.
J>Хотя, возможно, я неправильно понял твой вопрос.
Спасибо, помедитирую.
Хочешь быть счастливым — будь им!
Без булдырабыз!!!
Здравствуйте, LaptevVV, Вы писали:
LVV>Вопрос не в том, что я сам не понимаю. Вопрос придумать простой и понятный пример для объяснения студентам.
могу дать такие советы:
1) взять чье-либо объяснение и его разжевать. это проще, чем что-то самому выдумывать
2) для меня move — это аналог swap, просто реализованный на уровне языка (пусть и грубая аналогия, но для меня она первична). теперь задача сводиться к тому, чтобы объяснить зачем нужен swap =)
Здравствуйте, LaptevVV, Вы писали:
LVV>Здравствуйте, uzhas, Вы писали:
U>>Здравствуйте, LaptevVV, Вы писали:
LVV>>>Вот как раз про вектор — проясните, что тут имеется ввиду. LVV>>>У стандартного вектора — семантика копирования. LVV>>>В каких ситуациях может понадобиться семантика переноса?
U>>какой у тебя вообще уровень понимания ? LVV> LVV>Ну, например, что такое ленивые вычисления — я прекрасно понимаю. И могу реализовать. LVV>Проблем при чтении Александреску — не испытывал... U>>рекомендую начать с вики или SO: http://stackoverflow.com/questions/3106110/what-are-move-semantics LVV>Спасибо, посмотрю. LVV>Вопрос не в том, что я сам не понимаю. Вопрос придумать простой и понятный пример для объяснения студентам. LVV>Вот с виртуальностью я долго мучился с примерами, пока не нашел пример у Шамиса в его книжке по С++Builder — очень просто и понятный... LVV>И студенты с тех пор — тоже прекрасно СРАЗУ усваивают. LVV>Вот и для семантики перехода хотелось бы придумать-поиметь аналогичный простой пример.
Попробуйте рассмотреть на примере std::swap для двух векторов (ну, или любых других объектов).
Ну и, как вариант, эстафетное владение. Т.е. unique_ptr или подобный объект.
Здравствуйте, LaptevVV, Вы писали:
LVV>Изучаю тут С++ для преподавания студентам. LVV>И столкнулся с одной проблемой: не могу найти простого вменяемого примера для объяснения семантики переноса.
Эстафетное владение указуемым объектом (std::auto_ptr). В одном месте создали, в другое отдали, в старом забыли.
Физический пример: тачка с песком.
Передача по значению — это сделать новую тачку и насыпать туда такой же песок, как в исходной. Будет вдвое больше песка.
Передача по ссылке — отдать свою тачку в аренду.
Перемещение — высыпать песок из одной тачки в другую. Старую тачку выкинуть не немедленно, а в наиболее подходящий момент.
Можешь предложить студентам творческое задание: как передавать внутрь функции или возвращать из функции тяжеловесный объект (скажем, рукодельный вектор), не прибегая ни к копированию, ни к подсчёту ссылок. (А то на shared_ptr любой дурак сделает).
Скажи, что это нужно, скажем, для библиотеки линейной алгебры.
Здравствуйте, LaptevVV, Вы писали:
LVV>Не могли бы наши гуру предложить простой (дубовый, кондовый... ), но в то же время точно отражающий семантику переноса пример? LVV>Буду весьма признателен.
Голливудский сценарий:
Простой инженер КБ, будучи завербованным агентом иностранной разведки, выносит с работы USB флэшку (объект) набитую сверхсекретными чертежами и спецификациями. Он отдает (move) ее агенту, получает чемодан денег и оба скрываются во мраке ночи (ибо копировать 100500 гигов с флэшки в ноут агента очень долго, да и флэшка (как носитель) не стоит такого риска). Агент сам не может вывезти ее через границу, поэтому одтает (move) флэшку (объект) дип. курьеру в консульстве своей страны. В полете у курьера случается сердечный приступ, а флэшка (объект) попадает (move) в руки стюардессы -- агента военной разведки третьей страны, которая подсыпала что-то в стакан курьеру. Сразу после приземления, стюардесса едет на конспиративную квартиру, где происходит копирование (та самая процедура, которая занимает 100500 минут) этой флэшки на несколько других носителей. После чего, флэшка уже не нужна и уничтожается (delete flashka.
Дальше, по закону жанра, в кадре появляется главный герой, который до конца фильма мочит (terminate) всех, кто хоть что-то знает про информацию с флэшки %)
перемещая (move) всех на своем пути в мир иной %)
Здравствуйте, zaufi, Вы писали:
Z>Дальше, по закону жанра, в кадре появляется главный герой, который до конца фильма мочит (terminate) всех, кто хоть что-то знает про информацию с флэшки %) Z>перемещая (move) всех на своем пути в мир иной %)
Здравствуйте, LaptevVV, Вы писали:
LVV>В каких ситуациях может понадобиться семантика переноса?
Возьми как пример вектор строк.
И ситуацию, когда этот вектор должен выполнить релокацию — добавили новую строку, а места в буфере вектора для нее нет.
Как выполняется релокация с обычным копированием:
1. создается новый буфер увеличенного размера,
2. строки из старого буфера копируются в новый (создаются копии),
3. строки в старом буфере удаляются,
4. удаляется старый буфер.
Копирование одной строки на шаге 2 влечет создание буфера в новом объекте-строке и копирование содержимого из старого в новый.
При этом, буфер старого объекта-строки просто удаляется на шаге 3.
Вместо этого, новый объект-строка мог бы просто забрать себе адрес массива символов старого объекта, а указатель на этот массив в старом объекте-строке просто занулить, чтобы деструктор не удалил этот массив.
Это и делает семантика переноса класса строки.
Здравствуйте, LaptevVV, Вы писали:
LVV>Изучаю тут С++ для преподавания студентам. LVV>И столкнулся с одной проблемой: не могу найти простого вменяемого примера для объяснения семантики переноса. LVV>Теоретически все понятно: кода мы переносим файл, то сам файл с места не сдвигается, а меняется только управляющая информация.
как мне кажется, тут надо начинать с категорий выражений — всякие xvalue/prvalue
потом изучать как передаются параметры в конструкторы и прочие функции
потом становится понятно что если в функцию передали неконстантную ссылку, то мы можем взять из нее одни данные, а взамен положить другие
а дальше и перенос становится логичным и понятным
или может стоит сначала рассказать про замену копирования на swap
void f(X& a) {
X b = ...;
swap(a, b);
}
а потом сказать что вместо вызова swap руками есть мув-семантика
Здравствуйте, Abyx, Вы писали:
A>или может стоит сначала рассказать про замену копирования на swap A>а потом сказать что вместо вызова swap руками есть мув-семантика
Вот! Кажись то, что нужно!
Хочешь быть счастливым — будь им!
Без булдырабыз!!!
Здравствуйте, LaptevVV, Вы писали:
LVV>Не могли бы наши гуру предложить простой (дубовый, кондовый... ), но в то же время точно отражающий семантику переноса пример? LVV>Особенно обращаюсь к Кодту и Павлу Кузнецову — может быть вам уже приходилось это использовать и/или объяснять? LVV>Буду весьма признателен.
А мне кажется, с методической точки зрения, лучше взять в качестве объекта для переноса std::string. Вот, почему раньше нехорошо было писать такие функции:
Потому, что создавалось два объекта std::string: один во фрейме вызова foo, а другой -- копия этого объекта для возврата результата вызова. Поэтому, раньше делали, например, так:
void foo(std::string& result) {
result = "Hello, World!";
}
Сейчас можно так не делать, т.к. компилятор умный, он видит в первом варианте foo, что переменная result больше не нужна, поэтому ее можно изменить в целях оптимизации. Формальное определение такого изменения -- это преобразование к r-value ссылке. Для r-value ссылки у std::string есть специальный конструктор, который не резервирует новую память для хранения "Hello, World!", а берет из параметра конструктора (т.е. из r-value ссылки на существующий объект) имеющуюся у него память, ту же строку "Hello, World!". Т.к. на два объекта типа std::string не может быть одной памяти, содержащей "Hello, World!" (знаю, есть (были) библиотеки, где это не так, но студентам не нужно об этом рассказывать, если не спросят), то модифицируется содержимое объекта из параметра: у него нагло забирается указатель на память "Hello, World!". Выделения новой памяти нет, копирования нет -- все оптимально.
Если объект для перемещения, это не возвращаемый параметр, а переменная, которая будет доступна после перемещения, например:
....
std::string str1 = "Hello, World!";
std::string str2 = str1;
....
// где-то здесь можно использовать str1
}
то такие вопросы студентам:
1. Как подсказать компилятору, что при инициализации str2 надо не копировать данные из str1, а переместить?
2. Что будет с str1 после перемещения и можно ли этот объект использовать снова?
Здравствуйте, uzhas, Вы писали:
U>Здравствуйте, LaptevVV, Вы писали:
LVV>>Вопрос не в том, что я сам не понимаю. Вопрос придумать простой и понятный пример для объяснения студентам.
U>могу дать такие советы: U>1) взять чье-либо объяснение и его разжевать. это проще, чем что-то самому выдумывать
Так он, вроде, именно "чье-либо объяснение" и просит
U>2) для меня move — это аналог swap, просто реализованный на уровне языка (пусть и грубая аналогия, но для меня она первична). теперь задача сводиться к тому, чтобы объяснить зачем нужен swap =)
Здравствуйте, LaptevVV, Вы писали:
LVV>В каких ситуациях может понадобиться семантика переноса?
При оптимизации операции swap и её подобных.
Про std::vector я уверен имелось в виду про оптимизацию переноса элементов в новое физическое хранилище при изменении размеров вектора.
Я, например, тоже с таким сталкивался, делал объект "набор данных БД", состоящий из строк из реляционных таблиц.
При этом типичная проблема -- это заполнение набора данных из запроса, размер строк предопределён сразу после выполнения запроса,
а размер всего набора данных неизвестен. Реализуется схема работы "выделяем буфер под n строк, читаем в него, пока не заполнится,
затем если переполняется, выделяем новый буфер в 2*n и копируем (перемещаем) уже прочитанное туда и продолжаем заполнять элементы
в конец, пока снова не достигнем границы, и так далее".
Делал это сам, руками (хранил ссылки на строки, владение передавалось от одного объекта "набор данных" другому), естественно,
никакой move semantics не было, а это -- самое оно, что нужно в таком случае.
Ну и все подобные алгоритмы подойдут, чтение в память строк текста из файла бесконечной длины,
приём сообщений в локальный буфер, и т.п.
Здравствуйте, Кодт, Вы писали:
К>Физический пример: тачка с песком. К>Передача по значению — это сделать новую тачку и насыпать туда такой же песок, как в исходной. Будет вдвое больше песка. К>Передача по ссылке — отдать свою тачку в аренду. К>Перемещение — высыпать песок из одной тачки в другую. Старую тачку выкинуть не немедленно, а в наиболее подходящий момент.
Удачней пример с телевизором:
По значению — вот тебе точно такой-же телевизор, бери и смотри. Можешь каналы переключать, мне все равно.
По ссылке — садись, вместе будем смотреть, при этом если программу кто-то переключит, то все смотреть будут другой канал.
Перемещение — да забирай ты этот телевизор себе, надоел он мне.
Походу самый простой способ понять move семантику — это на примере кода. Давайте начнём с очень простого класса строк, который всего лишь хранит указатель на размещённый в куче блок памяти:
#include <cstring>
#include <algorithm>
class string
{
char* data;
public:
string(const char* p)
{
size_t size = strlen(p) + 1;
data = new char[size];
memcpy(data, p, size);
}
Так как мы решили самостоятельно управлять памятью, нам нужно следовать Правилу трёх. Пока опустим оператор присваивания и напишем деструктор и конструктор копирования:
Параметр конструктора const string& подходит ко всем выражениям с типом string, что позволяет нам создавать копии в следующих примерах:
string a(x); // Line 1
string b(x + y); // Line 2
string c(some_function_returning_a_string()); // Line 3
Терь наступает ключевой момент в понимании мув семантики. Обратите внимание, что только в первой строке, где мы копировали x, глубокая копия на самом деле необходима, потому, что мы можем использовать x позже, и будем сильно удивлены, если x как то изменится. Заметили как я только что 3 раза сказал x (четыре, если считать это предложение) и это каждый раз означало тот же объект? Мы называем такие выражения, как x "lvalue"
Аргументы в строках 2 и 3 это не lvalue, а rvalue, поскольку эти строковые объекты не имеют имён, и вызывающий код не имеет возможности дальнейшего их использования. rvalue это временные объекты, которые уничтожаются в конце выражения. Это важно, поскольку во время инициализации b и c мы можем делать что угодно с исходными строками и вызывающий код об этом никак не узнает.
C++0x предаставляет новый механизм называющийся "rvalue reference" который помимо прочего, через перегрузку функций, позволяет нам определять что аргумент является rvalue. Всё что, нам для этого надо — это написать конструктор с параметром
ссылкой на rvalue. Внутри этого конструктора мы можем делать всё что угодно с исходным значением, пока оно остаётся в каком то валидном состоянии
string(string&& that) // string&& это rvalue ссылка на string
{
data = that.data;
that.data = 0;
}
Ну и что мы тут сделали? Вместо глубокой копии данных из кучи мы просто скопировали указатели и установили оригинальный указатель в null. В результате мы "стащили" данные, которые изначально принадлежали исходной строке. Опять же ключевой момент в том что вызывающий код ни при каких обстоятельствах не узнает, что исходный объект был изменён. Поскольку на самом деле мы не создавали копию, такой конструктор называется "move constructor". Его работа заключается в том чтобы, вместо копирования, просто переместить ресурсы из одного объекта в другой.
Поздравляю, вы только что поняли основы move семантики. Давайте продолжим реализовав оператора присваивания. Если вы пока не знакомы с copy and swap идиомой то почитайте и возвращайтесь назад, поскольку это довольно грамотный подход, для того чтобы обеспечивать безопасность исключений в c++
Опа. Ну и чё это? Где здесь "rvalue reference"? кто то спросит. А я отвечу "А оно нам тут и не надо )"
Заметьте мы тут передали параметр по значению, так чтобы он инициализировался как любой другой строковый объект. Ну и как он будет инициализироваться? Раньше, во времена C++98, ответ был бы "с помощью конструктора копирования". В C++0x компилятор уже выбирает между конструктором копирования и конструктором переноса на основе того является аргумент lvalue или rvalue.
Так, если вы напишете a = b "that" будет инициализировано конструктором копирования (по тому, что b это lvalue) и оператор присваивания обменяется содержимым с только что созданной копией. Вот и всё определение идиомы copy and swap — создать копию, обменяться содержимым с копией, а потом избавиться от копии выходом из области видимости. Здесь больше ничего нового.
Но если вы напишете a = x + y "that" будет инициализировано с помощью конструктора переноса (т.к. x + y это rvalue), таким образом глубокая копия не создаётся, только переходит владение. "that" всё ещё независимый от аргументов объект, но его создание было дешевым, так как данные из кучи не копировались, а только меняли владельца.
Итого: Конструктор копирования создаёт глубокую копию, для того чтобы исходные данные оставались нетронутыми. Конструктор перемещения может копировать только указатель, и после этого установить указатель и исходном объекте в null. Занулять указатель в исходном объекте надо, иначе данные будут удалены слишком рано — в деструкторе временного объекта, да ещё и второй раз — в деструкторе объекта, куда мы их перетащили.
Ну и слегка поподробнее:
Введение
Семантика премещения позволяет, при определенных условиях, получать во владение ресурсы какого либо другого объекта. Это важно в двух случаях:
1. Замена дорогого копирования дешевой сменой владельца. Обратите внимание, что если объект не управляет по крайней мере одним внешним ресурсом (непосредственно, или через другой объект его член) семантика копирования не представляет никакого преимущества перед семантикой копирования. В этом случае копирование и перемещение — одно и то же:
class cannot_benefit_from_move_semantics
{
int a; // перемещение инта тоже самое что и копирование интаchar d[64]; // перемещение массива символов тоже самое что и копирование массива символов
// ...
};
2. Реализация безопасных "move-only" типов; это типы для которых копирование не имеет смысла а перемещение имеет. Например файловые хендлеры, смартпоинтеры с семантикой уникального владения.
Что значит перемещение?
Стандартная библиотека С++98 предоставляет смартпоинтер с семантикой уникального владения std::auto_ptr<T>, который гарантирует что динамически размещённый объект будет удалён даже в случае исключения:
{
std::auto_ptr<Shape> a(new Triangle);
// ...
// arbitrary code, could throw exceptions
// ...
} // <--- when a goes out of scope, the triangle is deleted automatically
необычность auto_ptr состоит в его "копирующем" поведении:
auto_ptr<Shape> a(new Triangle);
+---------------+
| triangle data |
+---------------+
^
|
|
|
+-----|---+
| +-|-+ |
a | p | | | |
| +---+ |
+---------+
auto_ptr<Shape> b(a);
+---------------+
| triangle data |
+---------------+
^
|
+----------------------+
|
+---------+ +-----|---+
| +---+ | | +-|-+ |
a | p | | | b | p | | | |
| +---+ | | +---+ |
+---------+ +---------+
Заметьте, что при инициализации b переменной a треугольник не копируется, вместо этого передаётся владение треугольником из a в b. Как ещё говорят "a переместилось в b" или "треугольник переместился из a в b". Это может звучать странно, поскольку треугольник сам по себе оставался на том же месте в памяти.
Переместить объект означает передать владение какого либо ресурса, которым он управлял, в другой объект.
Конструктор копирования auto_ptr скорее всего выглядит как то так (немного упрощённо):
auto_ptr(auto_ptr& source) // Обратите внимание нету const
{
p = source.p;
source.p = 0; // терь source больше не владеет объектом
}
Dangerous and harmless moves
Опасная фигня с auto_ptr заключается в том, что синтаксически это выглядит что копия на самом деле переместилась.
Попытка вызова функции-члена auto_ptr из которого перемещены данные приводит к неопределенному поведению, поэтому надо быть очень осторожным чтобы не использовать auto_ptr после того как из него произошло перемещение:
auto_ptr<Shape> a(new Triangle); // создаём треугольник
auto_ptr<Shape> b(a); // перемещаем из a в bdouble area = a->area(); // неопределенное поведение
But auto_ptr is not always dangerous. Factory functions are a perfectly fine use case for auto_ptr:
Но auto_ptr не всегда опасны. Фабричные функции — отличный юзкейс для auto_ptr:
auto_ptr<Shape> c(make_triangle()); // перемещаем временный объект в cdouble area = make_triangle()->area(); // всё безопасно
Обратите внимание, как оба примера следуют одной и той же семантике:
auto_ptr<Shape> variable(expression);
double area = expression->area();
И в то время как один из них вызывает неопределенное поведение другой нет. Так какая разница между expressions и make_triangle()? Они что, разного типа? Не, одного. Но они разных value категорий.
Value категории
Очевидно должно быть глубокое различие между переменной auto_ptr expression и выражением make_triangle(), которое является вызовом функции, возвращающей auto_ptr по значению, которая создаёт новый временный auto_ptr объект каждый раз, когда она вызывается. expression это пример lvalue, в то время как make_triangle() — пример rvalue.
Перемещение из lvalue как в пример выше опасно, потому, что мы можем позже вызвать функцию-член через a, что приведёт к неопределённому поведению. С другой стороны перемещение из rvalue совершенно безопасно потому, что после того как конструктор копирования завершил свою работу мы не можем снова использовать временный объект. Если мы просто напишем make_triangle() ещё раз, мы получим новый временный объект. Фактически временный объект, из которого производится перемещение уничтожается на следующей строке
auto_ptr<Shape> c(make_triangle());
^ временный объект, из которого производится перемещение умирает прямо тут
Обратите внимание, что буквы l и r исторически произошли от левой(left-hand) и правой(right-hand) стороны присваивания.
В С++ это больше не так потому, что существуют lvalue, которые не могут появиться с левой стороны присваивания (навроде массивов и типов определённых пользователем без оператора присваивания) и rvalue которые могут
Ссылки на rvalue
Теперь мы понимаем, что перемещение из lvalue потенциально опасно, а перемещение из rvalue нет. Если бы язык С++ давал возможность отличить lvalue аргументы от rvalue аргументов мы можем либо полностью запретить перемещение из lvaluе, либо по крайней мере сделать перемещение из lvalue явным в месте вызова, и таким образом мы избегаем случайного перемещения.
С++11 решает эту проблему ссылками на rvalue. rvalue ссылки это новый вид ссылок, которые связываются только с rvalue объектами, и имеют синтаксис X&&. Старые добрые X& ссылки теперь называются lvalue ссылками. (Заметьте что X&& это не ссылки на ссылки, такой штуки в С++ нету)
Если мы добавим ещё и const, то получим уже четыре различных типа ссылок. Тут перечислены виды выражений типа X, с которыми они могут быть связаны (bind).
lvalue const lvalue rvalue const rvalue
---------------------------------------------------------
X& да
const X& да да да да
X&& да
const X&& да да
На практике вы можете забыть про X&&. От rvalue только для чтения мало толку.
rvalue ссылки X&& это новый вид ссылок, которые привязываются (binds) только rvalue.
Неявные преобразования
rvalue ссылки прошли через нескольких версий. С версии 2.1 ссылки X&& также связываются со всеми value категориями отличного типа Y, если предоставлен способ неявного преобразования из Y в X. В этом случае создаётся временный объект типа X и rvalue ссылка связывается с этим временным объектом:
В примере, приведенном выше, "hello world" это lvalue имеющее тип const char[12]. Так как тут неявное преобразование из const char[12] через const char* в std::string, создаётся временный объект типа std::string, и r с ним связывается. Это один из случаев, когда разница между rvalues (выражениями) и временными объектами немного размыта.
Конструкторы перемещения
Полезный пример функции с параметром X&& это конструктор перемещения X::X(X&& source. Он предназначен для передачи владения ресурсом из объекта source в текущий объект.
В С++11 std::auto_ptr<T> заменен на std::unique_ptr<T>, который имеет преимущество rvalue ссылок. Мы тут создадим и обсудим упрощенную версию unique_ptr.
Сперва мы инкапсулируем сырой указатель и перегрузим операторы -> и *, так чтобы наш класс вёл себя как указатель:
Вторая строчка не компилируется потому что a это lvalue, а параметр unique_ptr&& принимает только rvalue. Это именно то, чего мы хотели. Опасное перемещение никогда больше не произойдёт неявно. Третья строка компилируется без проблем, так как make_triangle() это rvalue. Конструктор перемещения преренесёт владение от временного объекта в c. Опять же это то что нам нужно.
Перемещающий конструктор передаёт владение управляемым ресурсом в текущий объект
Перемещающий оператор присваивания
Последняя недостающая часть это Перемещающий оператор присваивания. Его задача состоит в освобождении старого ресурса и захвате нового ресурса из аргумента:
unique_ptr& operator=(unique_ptr&& source) // rvalue ссылка
{
if (this != &source) // не присваиваем самому себе
{
delete ptr; // освобождаем старый
ptr = source.ptr; // захватываем новый
source.ptr = nullptr;
}
return *this;
}
};
Тут реализация перемещающего оператора присваивания дублирует логику деструктора и перемещающего конструктора.
Помните про copy-and-swap идиому? Она также может быть применена к семантике перемещения как move-and-swap идиома
Теперь source это переменная типа unique_ptr, она должна быть инициализирована конструктором перемещения; таким образом аргумент будет перемещен в параметр. Всё ещё требуется чтобы аргумент был rvalue, потому что конструктор перемещения сам по себе имеет параметр ссылку на rvalue. Когда порядок выполнения достигает закрывающей фигульной скобки оператора=, source выходит из области видимости и уничтожается, автоматически освобождая старый ресурс.
Перемещающий оператор присваивания передаёт владение управляемым ресурсом в текущий объект и освобождает старый ресурс. Идиома move-and-swap облегчает реализацию.
Перемещение из lvalue
Иногда нам хотелось бы переместить из lvalue. То есть мы хотели бы чтобы компилятор обращался с lvalue как будто это rvalue, так чтоб он мог вызвать конструктор перемещения, пусть это было бы потенциально небезопасно. Для этих целей С++11 предлагает функцию стандартной библиотеки std::move, которая находится в заголовке <utility>. Имя выбрано слегка неудачно, потому что std::move всего лишь приводит lvalue к rvalue, и ничего никуда не перемещает. Эта функция всего лишь делает возможным перемещение. Возможно она должна была называться std::cast_to_rvalue или std::enable_move, но в настоящее время название уже устаканилось.
Тут показано как вы можете перемещать из lvalue явно:
unique_ptr<Shape> a(new Triangle);
unique_ptr<Shape> b(a); // всё ещё ошибка
unique_ptr<Shape> c(std::move(a)); // okay
Обратите внимание, что после третьей строки a больше не владеет треугольником. Это нормально, поскольку явно написав std::move(a) мы сделали ясными наши намерения: "Уважаемый конструктор, делай что захочешь с a, чтобы инициализировать c; Меня а больше не интересует."
std::move(some_lvalue) приводит lvalue к rvalue, и делает возможным дальнейшее перемещение.
Xvalue
Обратите внимание, что хотя std::move(a) и rvalue, в результате не создаётся временный объект. Эта проблема вынудила комитет ввести третью категорию value. Это что то, что может быть связано с rvalue ссылкой, хотя оно и не rvalue в традиционном понимании, и назвали это xvalue (eXpiring value). А традиционное rvalue было переименовано в prvalue(Pure rvalues).
Оба prvalue и xvalue являются rvalues. Xvalue и lvalue оба являются glvalues (Generalized lvalues). Взаимотношение между ними проще всего можно представить на диаграмме:
Заметьте, что только xvalues является нововведением; остальные появились в результате переименования и группировки.
rvalues из C++98 в C++11 известны как prvalues. Мысленно замените все "rvalue" из предыдущих параграфов на "prvalue".
Перемещение из функций
До сих пор мы наблюдали только перемещение локальных переменных в параметры функций. Но перемещение так же возможно в обратном направлении. Если функция возвращает по значению, какой то объект в месте вызова (скорее всего локальный или временный, но может быть любой тип объекта) инициализируется выражением после оператора return как аргументом в конструкторе перемещения:
unique_ptr<Shape> make_triangle()
{
return unique_ptr<Shape>(new Triangle);
} \-----------------------------/
|
| временный объект перемещается в c
|
v
unique_ptr<Shape> c(make_triangle());
Возможно это покажется странным, автоматические объекты (локальные переменные, которые не были объявлены как static) так же могут быть перемещены из функций:
Как так получилось что конструктор перемещения принимает lvalue в качестве аргумента? Область видимости переменной result заканчивается и она должна будет уничтожен во время разворачивания стека. Когда управление возвращается в место вызова переменной result больше не существует. По этой причине С++11 имеет специальное правило, которое позволяет возвращать автоматические объекты из функций без вызова std::move. Вообще вы никогда не должны использовать std::move для перемещения автоматических объектов из функций, так как это препятствует оптимизации NRVO "named return value optimization"
Никогда не используйте std::move, чтобы переместить из функции.
Обратите внимание, что в обоих фабричных функциях возвращаемый тип это значение(value), не ссылка на rvalue. Rvalue ссылки — всё ещё остаются ссылками, и как обычно, вы никогда не должны возвращать ссылки на автоматические объекты. Вызывающая сторона будет иметь дело с висящими ссылками, если вы попытаетесь сделать так:
Никогда не возвращайте автоматические объекты по rvalue ссылке. Перемещение производится исключительно перемещающим конструктором, не вызовом std::move и не только лишь привязкой rvalue к ссылок на rvalue.
Перемещение в члены класса
Рано или поздно вы попытаетесь написать код навроде этого:
Обычно компилятор пожалуется на то что параметр lvalue. Если вы посмотрите на этот тип вы увидите ссылку на rvalue, но rvalue ссылка просто означает "ссылку, которая привязывается к rvalue"; Это не означает что сама ссылка является rvalue! В самом деле parameter это просто обычная переменная с именем. Вы можете использовать parameter столько раз, сколько вам нужно внутри тела конструктора, и это всегда будет относится к одному и тому же объекту. Неявное перемещение из него будет опасным, по этому оно запрещено языком.
Именованная rvalue ссылка является lvalue, как и любая другая переменная.
Решение в том, чтобы вручную разрешить перемещение:
class Foo
{
unique_ptr<Shape> member;
public:
Foo(unique_ptr<Shape>&& parameter)
: member(std::move(parameter)) // обратите внимание на std::move
{}
};
Вы можете возразить, что parameter больше не используется после инициализации члена. Почему тут нет специального правила пропускать вставку std::move как было с возвращаемым значением? Возможно потому, что это было бы слишком сложно для реализации компилятора. Например сто если тело конструктора в другой единице трансляции? В отличие от этого в возвращаемом значении гораздо проще по таблице символов определить есть ли идетнификаторы после оператора return, относящиеся к автоматическому объекту.
Вы также можете передать параметр по значению. Для move-only типов, таких как unique_ptr похоже пока не существует устоявшейся идиомы. Я предпочитаю передавать по значению, так как уменьшает количество путаницы в интерфейсе.
Специальные функции-члены
C++98 неявно определяет три специальных функции-члена: конструктор копирования, оператор присваивания и деструктор.
Как мы знаем rvalue ссылки прошли через нескольких версий. С версии 3.0 C++11 добавляет две дополнительных специальных функции-члена: конструктор перемещения и перемещающий оператор присваивания. Заметьте, что ни VC10 ни VC11 пока не поддерживают 3.0, поэтому мы будем реализовывать их самостоятельно.
эти две специальные функции объявляются неявно только если они не объявлены вручную. И ещё если вы определили свои собственные перемещающие конструктор или оператор присваивания неявные копирующие конструктор и оператор присваивания не объявляются.
Что эти правила означают на практике?
Если вы напишете класс без ресурсов, которыми нужно управлять, тогда нет необходимости в какой либо из пяти специальных функций, и у вас будет корректная семантика копирования и перемещения без вашего вмешательства. Иначе вам нужно реализовывать спецфункции самостоятельно. И конечно же если ваш класс не получает преимуществ от семантики перемещения, то нет необходимости в реализации специальных операций перемещения.
Заметьте, что копирующий оператор присваивания и перемещающий оператор присваивания могут быть объединены в один унифицированный оператор присваивания, принимающий аргумент по значению:
Таким образом число спецфункций уменьшается до пяти.
Универсальные ссылки
Посмотрите на следующую шаблонную функцию:
template<typename T>
void foo(T&&);
Вы можете ожидать что T&& привязывается только к rvalues, потому, что на первый взгляд это выглядит как ссылка на rvalue. Как оказывается T&& также связывается с lvalue
foo(make_triangle()); // T является unique_ptr<Shape>, T&& является unique_ptr<Shape>&&
unique_ptr<Shape> a(new Triangle);
foo(a); // T является unique_ptr<Shape>&, T&& является unique_ptr<Shape>&
Если аргумент это rvalue типа X, T становится типом X, и тут T&& означает X&&. Это ожидаемо. Но если аргумент lvalue типа X, благодаря специальному правилу T становится типом X&, отсюда T&& будет означать что то вроде X& &&. Но так как ссылок на ссылки в C++ нет тип X& && сокращается до X&
По началу это может звучать сбивающим с толку, такое сокращение ссылок предназначено для обеспечения возможности идеальной передачи(perfect forwarding)
T&& это не rvalue ссылка, а универсальная ссылка. Она также привязывается к lvalue, в этом случае T и T&& оба ссылки на lvalue.
Реализация перемещения
Теперь, когда вы понимаете сокращение ссылок (reference collapsing), посмотрим как реализована std::move
Как вы видите move принимает параметр любого типа, благодаря универсальной ссылке T&& и возвращает ссылку на rvalue. Вызов мета-функции std::remove_reference<T>::type необходим, иначе для lvalue типа X, возвращаемый тип будет X& &&, который сократится до X&. Так как это всегда lvalue (вспомните, что именованная rvalue ссылка является lvalue) но мы хотим привязать t к ссылке на rvalue, мы явно приводим t к корректному возвращаемому типу. Вызов функции, возвращающей ссылку на rvalue сам по себе является xvalue.
Вызов функции, возвращающей ссылку на rvalue такой, как td::move является xvalue.
C>Походу самый простой способ понять move семантику — это на примере кода. Давайте начнём с очень простого класса строк, который всего лишь хранит указатель на размещённый в куче блок памяти:
Вот что интересно.
Мне не нужно языкового механизма, чтобы реализовать семантику перемещения в классе, управляющем памятью.
Собственно, МастерЗив тут уже написал об этом.
Тогда возникает вопрос: зачем внесли в язык то, что реализовывалось и без оного?
Хочешь быть счастливым — будь им!
Без булдырабыз!!!
Здравствуйте, LaptevVV, Вы писали:
LVV>Тогда возникает вопрос: зачем внесли в язык то, что реализовывалось и без оного?
Чтобы разнести механизмы. Вам знакома страшная (уже не страшная) идиома COAP (container of autoptr)? Фишка была в том, что никакой возможности различить что же происходит, copy или move было нельзя. В этом плане unique_ptr безопасен именно в связи с тем, что появился механизм rvalue-ссылок на уровне языка
std::auto_ptr<int> p(new int);
std::auto_ptr<int> p2 = p; /* мамо, чому я зипсував p? */
но
std::unique_ptr<int> p(new int);
std::unique_ptr<int> p2 = std::move(p); /* тю! так це ж перемищення! */
и что особенно важно, вот так уже нельзя:
std::unique_ptr<int> p2 = p; /* gang-bang (!) */
То есть какой-нибудь std::vector внутри себя попытавшись скопировать такой объект отвалится на этапе компиляции, а не породит рандомное поведение при исполнении.
Минус полшанса острелить себе ногу.
А потом успевающим студентам можно задать на дом вопросы:
1) Почему нельзя безопасно сделать autoptr of array типа std::auto_ptr<int*> p(new int[10])?
2) Как rvalue refs помогают сделать unique_ptr of array безопасным?
Здравствуйте, LaptevVV, Вы писали:
LVV>Здравствуйте, Abyx, Вы писали:
A>>или может стоит сначала рассказать про замену копирования на swap A>>а потом сказать что вместо вызова swap руками есть мув-семантика LVV>Вот! Кажись то, что нужно!
Да, в любом случае надо объяснять идиому swap.
А потом уже механизм move как а) обобщение swap и как б) поддержку применимости swap на уровне языка (rvalue ссылки).
Пример со сложением двух строк (какой-то простой класс string в котором string owns char vector exclusively)
string a = "abc";
string b = a;
string c = a + b;
мне кажется наиболее показательным.
Там достаточно наглядно можно показать сколько памяти требуется для выполнения этих трех строк без и c rvalue.
И почему, скажем, swap (как механизм делания move руками) в данном случае применить нельзя без введения дополнительных сущностей (типа temporary_string костылей и пр.)
На этом же примере и RVO можно, кстати, объяснить.
Здравствуйте, LaptevVV, Вы писали:
LVV>Не могли бы наши гуру предложить простой (дубовый, кондовый... ), но в то же время точно отражающий семантику переноса пример? LVV>Особенно обращаюсь к Кодту и Павлу Кузнецову — может быть вам уже приходилось это использовать и/или объяснять? LVV>Буду весьма признателен.
На уровне метафор -- передали корзину с яблоками от одного владельца другому. Тогда как при копировании возникает дубль корзины.
Поэтому перенос, как правило, возможен как эффективная операция (O(1) noexcept, только переписать собственника в документах на владение), а копирование -- нет.
Здравствуйте, LaptevVV, Вы писали:
LVV>Изучаю тут С++ для преподавания студентам. LVV>И столкнулся с одной проблемой: не могу найти простого вменяемого примера для объяснения семантики переноса. LVV>Теоретически все понятно: кода мы переносим файл, то сам файл с места не сдвигается, а меняется только управляющая информация.
Здравствуйте, Rinbe, Вы писали:
R>Здравствуйте, LaptevVV, Вы писали:
LVV>>Изучаю тут С++ для преподавания студентам. LVV>>И столкнулся с одной проблемой: не могу найти простого вменяемого примера для объяснения семантики переноса. LVV>>Теоретически все понятно: кода мы переносим файл, то сам файл с места не сдвигается, а меняется только управляющая информация.
R>Я чего то не понимаю, это разве не твоя книга: C++. Экспресс курс
Повторяю еще раз — на уровне метафор все понятно.
Не хватает пока конкретного ПРОСТОГО примера для показа учням.
Но вроде потихоньку вырисовывается.
Поднял еще раз Стенли Липпмана — там МНОГО места посвящено проблемам swap().
Именно то, что нужно.
Хочешь быть счастливым — будь им!
Без булдырабыз!!!
Здравствуйте, LaptevVV, Вы писали:
LVV>Вот что интересно.
а мне интересно как такая простая вещь может быть интересна. LVV>Мне не нужно языкового механизма, чтобы реализовать семантику перемещения в классе, управляющем памятью. LVV>Собственно, МастерЗив тут уже написал об этом. LVV>Тогда возникает вопрос: зачем внесли в язык то, что реализовывалось и без оного?
я уже не раз говорил — если надо знать зачем та или иная фича в C++1x — читайте proposal.
template <class T> void Swap(T &a,T &b); // уже реализована как-тоtemplate <class T>
void MoveBySwap(T &dst,T &src) // перемещение существующих объектов
{
T temp{}; // объект с нулевым состоянием
Swap(dst,src);
Swap(src,temp);
}
template <class T>
T * CreateMoveBySwap(void *mem,T &src) // создание нового объекта перемещением старого
{
T *ret=new(mem) T{};
Swap(*ret,src);
return ret;
}
Здравствуйте, LaptevVV, Вы писали:
LVV>Тогда возникает вопрос: зачем внесли в язык то, что реализовывалось и без оного?
Что бы скомпенсировать ошибку дизайна STL...
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Сообразил, как рассказывать студентам.
1. Надо делать класс-контейнер, в котором требуется перегрузить конструктор копирования и операцию присваивания.
И обычным образом объяснить семантику значений. Ну, что-то вроде реализации вектора или дека основе динамического массива.
2. Потом возникает вопрос: а если элементами этого контейнера будут такие объекты, которые сами используют динамическую память?
Например, строки — контейнер символом в динамическом массиве.
Показать, что в семантике копирования дофига получается лишней работы по созданию-копированию-уничтожению временных объектов.
3. Показываем решение — просто переприсвоить поля-указатели.
Сами элементы не копировались-дублировались, а только необходимые управляющие поля.
Вот оно!
Хочешь быть счастливым — будь им!
Без булдырабыз!!!
Здравствуйте, LaptevVV, Вы писали:
LVV>Сообразил, как рассказывать студентам. LVV>1. Надо делать класс-контейнер, в котором требуется перегрузить конструктор копирования и операцию присваивания. LVV>И обычным образом объяснить семантику значений. Ну, что-то вроде реализации вектора или дека основе динамического массива. LVV>2. Потом возникает вопрос: а если элементами этого контейнера будут такие объекты, которые сами используют динамическую память? LVV>Например, строки — контейнер символом в динамическом массиве. LVV>Показать, что в семантике копирования дофига получается лишней работы по созданию-копированию-уничтожению временных объектов. LVV>3. Показываем решение — просто переприсвоить поля-указатели. LVV>Сами элементы не копировались-дублировались, а только необходимые управляющие поля. LVV>Вот оно!
Здравствуйте, Abyx, Вы писали:
LVV>>Вот что интересно. A>а мне интересно как такая простая вещь может быть интересна. LVV>>Мне не нужно языкового механизма, чтобы реализовать семантику перемещения в классе, управляющем памятью. LVV>>Собственно, МастерЗив тут уже написал об этом. LVV>>Тогда возникает вопрос: зачем внесли в язык то, что реализовывалось и без оного? A>я уже не раз говорил — если надо знать зачем та или иная фича в C++1x — читайте proposal.
Прочита я proposal. Ну и? Мне по-прежнему не понятно, зачем нужна семантика перемещения. Вернее так: я понимаю, что с этой новой семантикой сильно сокращается необходимый объём кода. Но я так и не понял, чего нельзя сделать без её использования.
Хотите перемещать временные объекты? Ну и что этому мешает?
Здравствуйте, B0FEE664, Вы писали:
BFE>Почему это сделали фичей языка, а не библиотечным методом из proposal не ясно.
Потому что задача типовая, и удваивать сущности (auto_ptr и auto_ptr_ref) в каждом таком случае — удовольствия никакого.
Опять же,
— дефолтная реализация перемещения (то есть, копирование) доступна из коробки — не надо патчить все типы подряд
— компилятор может автоматически выводить тип ссылки из основного, и основной из ссылки; в случае с библиотеками это была бы дополнительная морока
Здравствуйте, B0FEE664, Вы писали:
BFE>Здравствуйте, Abyx, Вы писали:
LVV>>>Вот что интересно. A>>а мне интересно как такая простая вещь может быть интересна. LVV>>>Мне не нужно языкового механизма, чтобы реализовать семантику перемещения в классе, управляющем памятью. LVV>>>Собственно, МастерЗив тут уже написал об этом. LVV>>>Тогда возникает вопрос: зачем внесли в язык то, что реализовывалось и без оного? A>>я уже не раз говорил — если надо знать зачем та или иная фича в C++1x — читайте proposal.
BFE>Прочита я proposal. Ну и? Мне по-прежнему не понятно, зачем нужна семантика перемещения. Вернее так: я понимаю, что с этой новой семантикой сильно сокращается необходимый объём кода. Но я так и не понял, чего нельзя сделать без её использования.
BFE>Почему это сделали фичей языка, а не библиотечным методом из proposal не ясно.
В вашем случае нельзя перейти от string к RValueRefString. А если это сделать получиться что-то типа shared_ptr/intrusive_ptr. Как следствие нужно внимательно жонглировать типами (не то принял/вернул потерял производительность).
Ну и теряется универсальность, вы не смоежете написать универсальный std::swap.
Здравствуйте, Кодт, Вы писали:
BFE>>Почему это сделали фичей языка, а не библиотечным методом из proposal не ясно. К>Потому что задача типовая, и удваивать сущности (auto_ptr и auto_ptr_ref) в каждом таком случае — удовольствия никакого.
Ну, может, в каждом и не надо, тем более, что, если я правильно понимаю, для POD типов оптимизации не предусмотрено.
К>Опять же, К>- дефолтная реализация перемещения (то есть, копирование) доступна из коробки — не надо патчить все типы подряд К>- компилятор может автоматически выводить тип ссылки из основного, и основной из ссылки; в случае с библиотеками это была бы дополнительная морока
Всё так. Только вот из proposal это не ясно. Там говорится про какие-то ужасы о невозможности:
1. One of the most important applications for move semantics is to move from temporaries (rvalues). Copy constructor elision (NRVO) almost fixes this, but not quite. Sometimes elision is not possible. Other times even when NRVO is done, it is not sufficient: alternate algorithms are desirable when the source data is known to be an rvalue (e.g. string+string example to be discussed below).
2. Moving from const objects (even rvalues) must be prohibited. It is very difficult to distinguish between a const and an rvalue in the current language (not impossible ... auto_ptr pulls it off).
Кстати, вот такой код:
string str = str1 + str2 + str3;
можно существенно ускорить отложив операции сложения до момента присваивания, чего встроенная семантика переноса не гарантирует для длинных строк. И получается, что задача так до конца и не решена.
Здравствуйте, B0FEE664, Вы писали:
BFE>Хотите перемещать временные объекты? Ну и что этому мешает? BFE>
BFE>какой-то говнокод
BFE>
ничего что твой говнокод невалиден?
ты не можешь делать l-value референс на временный объект
судя по _getch ты используешь VC++, и значит ты там говоришь
"зачем в С++11 расширили стандарт С++03, если можно сделать то же самое на С++03, но только с расширениями стандарта С++03"?
я уже вроде писал выше, что тут вся суть в добавлении новых категорий выражений, которые позволяют безопасно биндить временные объекты к мутабельным ссылкам,
казалось бы простая штука — и всеравно люди не понимают. как так?
Здравствуйте, saf_e, Вы писали:
BFE>>Прочита я proposal. Ну и? Мне по-прежнему не понятно, зачем нужна семантика перемещения. Вернее так: я понимаю, что с этой новой семантикой сильно сокращается необходимый объём кода. Но я так и не понял, чего нельзя сделать без её использования.
_>В вашем случае нельзя перейти от string к RValueRefString. А если это сделать получиться что-то типа shared_ptr/intrusive_ptr.
Не обязательно. Можно написать аналог std::move. Дело не хитрое:
потерял в производительности.
_>Ну и теряется универсальность, вы не смоежете написать универсальный std::swap.
Конечно нет. Я этого и не утверждал.
_>Опять-таки не слова о perfect-forwarding.
Но ведь perfect-forwarding — не главная причина введения семантики перемещения?
Здравствуйте, B0FEE664, Вы писали:
BFE>Кстати, вот такой код: BFE>string str = str1 + str2 + str3; BFE>можно существенно ускорить отложив операции сложения до момента присваивания, чего встроенная семантика переноса не гарантирует для длинных строк. И получается, что задача так до конца и не решена.
Этак мы до хаскелла и идриса доберёмся, с их формальными преобразованиями программ.
Существенное ускорение там можно сделать в три этапа:
— приведение к acc += str1; acc += str2; acc += str3; str = move(acc);
— приведение к len = str1.size()+str2.size()+str3.size(); acc.reserve(len); ...
— полная ленивость: если нет str =, то остаться в виде expression template
Здравствуйте, B0FEE664, Вы писали:
BFE>Здравствуйте, saf_e, Вы писали:
BFE>>>Прочита я proposal. Ну и? Мне по-прежнему не понятно, зачем нужна семантика перемещения. Вернее так: я понимаю, что с этой новой семантикой сильно сокращается необходимый объём кода. Но я так и не понял, чего нельзя сделать без её использования.
_>>Как следствие нужно внимательно жонглировать типами (не то принял/вернул потерял производительность).
BFE>Так и с новой семантикой перемещения дела обстоят точно так же, забыл добавить оператор: BFE>
Ну, тип становится всего один, и при возврате не нужно писать &&
_>>Ну и теряется универсальность, вы не смоежете написать универсальный std::swap. BFE>Конечно нет. Я этого и не утверждал.
_>>Опять-таки не слова о perfect-forwarding. BFE>Но ведь perfect-forwarding — не главная причина введения семантики перемещения?
Ну, собственно, к чему веду и я и все остальные. Фича нужная и позволяет меньшими усилиями писать производительный код там, где раньше это было или тяжело или невозможно.
Здравствуйте, Abyx, Вы писали:
BFE>>Хотите перемещать временные объекты? Ну и что этому мешает? BFE>>
BFE>>какой-то говнокод
BFE>>
A>ничего что твой говнокод невалиден?
Где?
A>ты не можешь делать l-value референс на временный объект
И какое отношение это имеет к коду?
A>судя по _getch ты используешь VC++, и значит ты там говоришь
я много чего использую.
A>"зачем в С++11 расширили стандарт С++03, если можно сделать то же самое на С++03, но только с расширениями стандарта С++03"?
Нет. Я говорю, что отсыл на proposal не является ответом на вопрос Лаптева.
A>я уже вроде писал выше, что тут вся суть в добавлении новых категорий выражений, которые позволяют безопасно биндить временные объекты к мутабельным ссылкам,
Т.е. всё сводится к удобству и безопасности, а ни о какой новой функциональности речи не идёт?
A>казалось бы простая штука — и всеравно люди не понимают. как так?
Чего именно я не понимаю?
Здравствуйте, saf_e, Вы писали:
BFE>>>>Прочита я proposal. Ну и? Мне по-прежнему не понятно, зачем нужна семантика перемещения. Вернее так: я понимаю, что с этой новой семантикой сильно сокращается необходимый объём кода. Но я так и не понял, чего нельзя сделать без её использования.
_>>>Как следствие нужно внимательно жонглировать типами (не то принял/вернул потерял производительность). BFE>>Так и с новой семантикой перемещения дела обстоят точно так же, забыл добавить оператор: BFE>>
BFE>>потерял в производительности. _>Ну, тип становится всего один, и при возврате не нужно писать &&
В данном операторе наличие && у возвращаемого типа обеспечивает рост производительности
Здравствуйте, B0FEE664, Вы писали:
BFE>Здравствуйте, Abyx, Вы писали:
BFE>>>Хотите перемещать временные объекты? Ну и что этому мешает? BFE>>>
BFE>>>какой-то говнокод
BFE>>>
A>>ничего что твой говнокод невалиден? BFE>Где?
в компиляторе. твой код не компилируется.
A>>ты не можешь делать l-value референс на временный объект BFE>И какое отношение это имеет к коду?
да, у тебя временный объект (результат op+) передается в конструктор RValueRefString(RValueRefString& r)
это не валидный С++, и компилируется только за счет расширений VC++
A>>я уже вроде писал выше, что тут вся суть в добавлении новых категорий выражений, которые позволяют безопасно биндить временные объекты к мутабельным ссылкам, BFE>Т.е. всё сводится к удобству и безопасности, а ни о какой новой функциональности речи не идёт?
давай ты сначала напишешь свой код так чтобы он компилировался строго согласно С++03, а потом обсудим это еще раз.
рекомендую этот онлайн-компилятор — http://coliru.stacked-crooked.com/a/30fcce2f2edbf1cb
Всё круче! Можно, например, сделать массив потоков, пооткрывать их все прямо внутри массива, а потом массив заресайзить...
То есть можно двигать вообще некопируемые в принципе объекты,..
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Здравствуйте, Abyx, Вы писали:
A>>>ты не можешь делать l-value референс на временный объект BFE>>И какое отношение это имеет к коду? A>да, у тебя временный объект (результат op+) передается в конструктор RValueRefString(RValueRefString& r) A>это не валидный С++, и компилируется только за счет расширений VC++
О! Теперь я понял о чём речь. Я всё время забываю, что в стандарте есть этот прямой запрет.