Никогда не понимал move-семантику.
Вот лямбда-функции — понимал, ждал когда они появятся, и когда появились — стал активно использовать.
Концепты жду. Модули. Рефлексию.
А move-семантика.. она вдруг появилась, внезапно, я посмотрел что за зверь такой, нифига не понял и забил.
На практике никогда не возникала потребность в чем-то подобном. Есть же передача по указателям и по ссылкам.
И непонятно как другие языки обходятся без нее. Или не обходятся?
Ну да, мы словно бы говорим компилятору "мы не копируем переменную, а перемещаем ее". То есть объект в той ячейке, из которой мы его перемещаем, как-бы больше не нужно, и недействителен. Можно ли сказать, что это способ создать из инициализированных объектов неинициализированные? Синтаксически объект-то есть, а по смыслу вроде как и нет.
Здравствуйте, 00011011, Вы писали:
0>Никогда не понимал move-семантику. 0>Вот лямбда-функции — понимал, ждал когда они появятся, и когда появились — стал активно использовать. 0>Концепты жду. Модули. Рефлексию. 0>А move-семантика.. она вдруг появилась, внезапно, я посмотрел что за зверь такой, нифига не понял и забил. 0>На практике никогда не возникала потребность в чем-то подобном. Есть же передача по указателям и по ссылкам.
Простейший пример есть класс Foo с std::vector внутри,
но хочется этот std::vector наполнить значениями перед передачей в конструктор Foo,
а не засовывать в конструктор Foo сложную логику.
И при этом std::vector после конструирования Foo нам не нужен.
Можно конечно std::vector выделить в куче и передать по указателю,
но это уже будет не "абстракция с нулевой ценой".
0>И непонятно как другие языки обходятся без нее. 0>Или не обходятся?
В Rust она по умолчанию, а вот copy надо явно указывать.
Но там ошибка компиляции использование объекта после move.
Здравствуйте, 00011011, Вы писали:
0>И непонятно как другие языки обходятся без нее. Или не обходятся?
Rust не обходится. Там, конечно, по-другому, но всё же.
0>Можно ли сказать, что это способ создать из инициализированных объектов неинициализированные?
Всё-таки под неинициализированными кто-то может понимать объекты в некорректном состоянии. А move-семантику можно организовать так, чтобы исходный объект оставался корректным и готовым к переиспользованию. Скажем, после перемещения получался пустой контейнер.
0>На практике никогда не возникала потребность в чем-то подобном. Есть же передача по указателям и по ссылкам.
Но это не передача владения неким ресурсом (transferring ownership), это разделение владения (sharing ownership). У принимающей стороны не будет гарантий, что вызывающая сторона не оставила себе ссылку на передаваемый объект, и тем самым может нарушить инварианты вызываемой стороны.
Приведённый в соседнем сообщении пример: «Можно конечно std::vector выделить в куче и передать по указателю, но это уже будет не "абстракция с нулевой ценой".»
Проблема не столько в ненулевой цене абстракции, сколько в небезопасности. Если класс Foo хочет гарантировать свою внутреннюю целостность, то в случае передачи вектора по ссылке ему всё равно надо делать defensive copy. Так что он просто будет принимать вектор по значению. И уже вызывающая сторона принимает решение, переместить ли существующий вектор (тем самым отказываясь от владения его буфером чтобы избежать копирования) или оставить себе независимую копию.
Перемещения важны в двух случаях. Первый — оптимизация. Рассмотрим простейший случай конкатенации строк.
s4 = s1 + s2 + s3
Без использования мув-семантики нам придется при выполнении второго сложения выделить пямять, скопировать туда результат первого сложения, скопировать третью строку, освободить память от результата первого сложения -а потом выполнить еще одну копию внутри оператора присваивания. С мув-семантикой мы точно избегаем копирования при выполнении оператора присваивания, и потениально (если строка зарезервировала достаточно памяти) избегаем копирования при второй конкатенации.
Второй случай — семантически некопируемые объекты. Простейший пример — уникальный указатель. Копировать его запрещено (по определению), но перемещать — пожалуйста. Собственно, отсутствие в языке поддержки для перемещения приговорило к забвению древний auto_ptr.
можно объяснить очень поверхностно что бы войти в тему
дальше рекомендую взять и разобраться по какому то учебнику
что бы войти в тему необходимо понять как генерируется код на низком уровне
и что существует темповые переменные в результате генерирования низкоуровнего кода
к примеру на объектах будет понять проще
пусть A это будет std::vector, а функции его возвращают и принимают
1) A = someFuncRes();
2) someFunCall(getResFrom());
в двух данных примерах компилятор сгенерирует код с доп переменными класса А и операциями с классом A (можете поиграться на https://godbolt.org/)
это будут либо конструктор копирования класса А
либо оператор присваивания класса А
так вот
до С++11 везде во всех ситуациях генерировался либо
1) оператор копирования A(const A &)
2) оператор присваивания operator=(const A&)
в С++11 решили что на таких темповых ситуациях можно чертовски с экономить, ведь копировать весь вектор туда сюда очень долго и накладно
и поскольку темповые переменные создаются только компилятором и потом не нужны
то можно перехватывать такие операции над темповыми выражениями
были названы темповые переменные — rvalue и операции в классах над ними именуемыми операции перемещения
A(A &&)
operator=(A &&)
теперь компилятор видя ситуацию когда он создает темповую переменную класса с операциями инициализации или присваивания
генерирует код с операциями перемещения A(A &&) или operator=(A&&)
в которых программист может сам задать логику обработки
и на примере того же вектора, копировать вектор туда сюда нет смысла
достаточно все данные обменять в двух объектах, темпового (сгенерированного компилером и содержащий данные)
и результирующим
а в ситуациях когда компилер не генерирует код с операциями A(A &&) или operator=(A&&)
можно его принудительно заставить сделав static_cast<A &&>
в стандарте такой cast решили обернуть и назвать std::move
для принудительного вызова операций перемещения
Здравствуйте, Zhendos, Вы писали:
Z>Здравствуйте, 00011011, Вы писали:
0>>Никогда не понимал move-семантику. 0>>Вот лямбда-функции — понимал, ждал когда они появятся, и когда появились — стал активно использовать. 0>>Концепты жду. Модули. Рефлексию. 0>>А move-семантика.. она вдруг появилась, внезапно, я посмотрел что за зверь такой, нифига не понял и забил. 0>>На практике никогда не возникала потребность в чем-то подобном. Есть же передача по указателям и по ссылкам.
Z>Простейший пример есть класс Foo с std::vector внутри,
Вспомнил кстати пример проще и более популярный:
std::map<String, XYZ> map;
map.insert(std::pair<String, XYZ>("Move me if you can", ...));//1
И допустим у нас String реализована в лоб, то есть просто:
class String {
char *data;
size_t size;
size_t reserved;
};
с "new" в конструкторе и "delete []" в деструкторе.
Представляете сколько потенциально в (1) может быть выделений памяти и вызовов копирования массива памяти,
если бы не было move?
Здравствуйте, 00011011, Вы писали:
0>На практике никогда не возникала потребность в чем-то подобном. Есть же передача по указателям и по ссылкам. 0>И непонятно как другие языки обходятся без нее. Или не обходятся?
Мне перемещения не хватало, приходилось писать свои классы для временных переменных. Например, однажды я писал класс, объекты которого порождались в результате склеивания строк через операцию сложения. Довольно утомительное занятие, надо сказать.
0>Ну да, мы словно бы говорим компилятору "мы не копируем переменную, а перемещаем ее". То есть объект в той ячейке, из которой мы его перемещаем, как-бы больше не нужно, и недействителен. Можно ли сказать, что это способ создать из инициализированных объектов неинициализированные? Синтаксически объект-то есть, а по смыслу вроде как и нет.
Такой способ был бы логичным, но почему-то все пошли другим путём: std::move ничего не перемещает, а с объектами после перемещения можно продолжать работать. В результате операции перемещения не настолько эффективны, как могли бы быть: структуры и массивы по прежнему копируются вместо перемещения. Мы как бы говорим компилятору: перемести объект, если сможешь. Часто компилятор не может.
Здравствуйте, Zhendos, Вы писали:
Z>Но там ошибка компиляции использование объекта после move.
А для C++ такая ошибка компиляции имела бы смысл? Или в С++ намеренно не сделано этого?
МР>А для C++ такая ошибка компиляции имела бы смысл? Или в С++ намеренно не сделано этого?
В рамках существующих языковых конструктов это сделать невозможно. (хинт — как насчёт косвенных обращений у объекту?)
Здравствуйте, Максим Рогожин, Вы писали:
МР>Здравствуйте, Zhendos, Вы писали:
Z>>Но там ошибка компиляции использование объекта после move. МР>А для C++ такая ошибка компиляции имела бы смысл?
Конечно, она бы значительно упростила бы инварианты при написании класса,
если бы имелась бы гарантия на этапе компиляции, что после std::move будет вызван только деструктор.
А так приходится втыкать кучу assert на всякий случай.
> Или в С++ намеренно не сделано этого?
Ну разработчикам компиляторов C++ еще не всегда на вот это умеют выдавать ошибку компиляции
или хотя бы предупреждение:
void f()
{
int *i = new int;
delete i;
*i = 5;
}
а вы хотите чтобы они время жизни + std::move научились анализировать.
Здравствуйте, 00011011, Вы писали:
0>Никогда не понимал move-семантику.
помимо того, что уже ответили, отметил бы возможность использование кэш памяти. Для меня этот довод был основополагающим от игнорирования семантики перемещения до маниакального перемещения всего и вся.
Конструкции вида
std::vector<Object*>
могут располагаться в памяти как Бог на душу положит и обход списка в самом худшем случае будет идти минуя кэш сразу в оперативную память. При организации хранения в виде
std::vector<Object>
объекты расположены рядом и с большой вероятностью попадают в кэш (потому как загрузка из оперативной памяти происходит постранично).
Здравствуйте, Vamp, Вы писали:
V>В рамках существующих языковых конструктов это сделать невозможно. (хинт — как насчёт косвенных обращений у объекту?)
По ссылке или указателю, имеете ввиду? Ну а как в Rust с этим справились?
V>>В рамках существующих языковых конструктов это сделать невозможно. (хинт — как насчёт косвенных обращений у объекту?) МР>По ссылке или указателю, имеете ввиду? Ну а как в Rust с этим справились?
Я не большой знаток раста, но если я правильно понимаю, строгие правила ссылок не дают возможности переместить значение и обратиться к нему по другой ссылке, а указатели, свободные от этих ограничений, никаких гарантий не дают.