move-семантика
От: 00011011  
Дата: 01.01.19 10:20
Оценка:
Никогда не понимал move-семантику.
Вот лямбда-функции — понимал, ждал когда они появятся, и когда появились — стал активно использовать.
Концепты жду. Модули. Рефлексию.
А move-семантика.. она вдруг появилась, внезапно, я посмотрел что за зверь такой, нифига не понял и забил.
На практике никогда не возникала потребность в чем-то подобном. Есть же передача по указателям и по ссылкам.
И непонятно как другие языки обходятся без нее. Или не обходятся?

Ну да, мы словно бы говорим компилятору "мы не копируем переменную, а перемещаем ее". То есть объект в той ячейке, из которой мы его перемещаем, как-бы больше не нужно, и недействителен. Можно ли сказать, что это способ создать из инициализированных объектов неинициализированные? Синтаксически объект-то есть, а по смыслу вроде как и нет.
Re: move-семантика
От: Zhendos  
Дата: 01.01.19 11:05
Оценка:
Здравствуйте, 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.
Re: Гарантии
От: Qbit86 Кипр
Дата: 01.01.19 11:27
Оценка:
Здравствуйте, 00011011, Вы писали:

0>И непонятно как другие языки обходятся без нее. Или не обходятся?


Rust не обходится. Там, конечно, по-другому, но всё же.

0>Можно ли сказать, что это способ создать из инициализированных объектов неинициализированные?


Всё-таки под неинициализированными кто-то может понимать объекты в некорректном состоянии. А move-семантику можно организовать так, чтобы исходный объект оставался корректным и готовым к переиспользованию. Скажем, после перемещения получался пустой контейнер.

0>На практике никогда не возникала потребность в чем-то подобном. Есть же передача по указателям и по ссылкам.


Но это не передача владения неким ресурсом (transferring ownership), это разделение владения (sharing ownership). У принимающей стороны не будет гарантий, что вызывающая сторона не оставила себе ссылку на передаваемый объект, и тем самым может нарушить инварианты вызываемой стороны.

Приведённый в соседнем сообщении пример: «Можно конечно std::vector выделить в куче и передать по указателю, но это уже будет не "абстракция с нулевой ценой".»
Проблема не столько в ненулевой цене абстракции, сколько в небезопасности. Если класс Foo хочет гарантировать свою внутреннюю целостность, то в случае передачи вектора по ссылке ему всё равно надо делать defensive copy. Так что он просто будет принимать вектор по значению. И уже вызывающая сторона принимает решение, переместить ли существующий вектор (тем самым отказываясь от владения его буфером чтобы избежать копирования) или оставить себе независимую копию.
Глаза у меня добрые, но рубашка — смирительная!
Отредактировано 01.01.2019 11:29 Qbit86 . Предыдущая версия .
Re: move-семантика
От: Vamp Россия  
Дата: 01.01.19 14:29
Оценка:
Перемещения важны в двух случаях. Первый — оптимизация. Рассмотрим простейший случай конкатенации строк.

s4 = s1 + s2 + s3

Без использования мув-семантики нам придется при выполнении второго сложения выделить пямять, скопировать туда результат первого сложения, скопировать третью строку, освободить память от результата первого сложения -а потом выполнить еще одну копию внутри оператора присваивания. С мув-семантикой мы точно избегаем копирования при выполнении оператора присваивания, и потениально (если строка зарезервировала достаточно памяти) избегаем копирования при второй конкатенации.

Второй случай — семантически некопируемые объекты. Простейший пример — уникальный указатель. Копировать его запрещено (по определению), но перемещать — пожалуйста. Собственно, отсутствие в языке поддержки для перемещения приговорило к забвению древний auto_ptr.
Да здравствует мыло душистое и веревка пушистая.
Re: move-семантика
От: reversecode google
Дата: 01.01.19 17:24
Оценка:
можно объяснить очень поверхностно что бы войти в тему
дальше рекомендую взять и разобраться по какому то учебнику

что бы войти в тему необходимо понять как генерируется код на низком уровне
и что существует темповые переменные в результате генерирования низкоуровнего кода

к примеру на объектах будет понять проще
пусть 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
для принудительного вызова операций перемещения
Re[2]: move-семантика
От: Zhendos  
Дата: 02.01.19 08:45
Оценка:
Здравствуйте, 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?
Отредактировано 02.01.2019 8:46 Zhendos . Предыдущая версия .
Re: move-семантика
От: B0FEE664  
Дата: 02.01.19 13:06
Оценка:
Здравствуйте, 00011011, Вы писали:

0>На практике никогда не возникала потребность в чем-то подобном. Есть же передача по указателям и по ссылкам.

0>И непонятно как другие языки обходятся без нее. Или не обходятся?

Мне перемещения не хватало, приходилось писать свои классы для временных переменных. Например, однажды я писал класс, объекты которого порождались в результате склеивания строк через операцию сложения. Довольно утомительное занятие, надо сказать.

0>Ну да, мы словно бы говорим компилятору "мы не копируем переменную, а перемещаем ее". То есть объект в той ячейке, из которой мы его перемещаем, как-бы больше не нужно, и недействителен. Можно ли сказать, что это способ создать из инициализированных объектов неинициализированные? Синтаксически объект-то есть, а по смыслу вроде как и нет.


Такой способ был бы логичным, но почему-то все пошли другим путём: std::move ничего не перемещает, а с объектами после перемещения можно продолжать работать. В результате операции перемещения не настолько эффективны, как могли бы быть: структуры и массивы по прежнему копируются вместо перемещения. Мы как бы говорим компилятору: перемести объект, если сможешь. Часто компилятор не может.
И каждый день — без права на ошибку...
Re[2]: move-семантика
От: Максим Рогожин Россия  
Дата: 02.01.19 20:21
Оценка:
Здравствуйте, Zhendos, Вы писали:

Z>Но там ошибка компиляции использование объекта после move.

А для C++ такая ошибка компиляции имела бы смысл? Или в С++ намеренно не сделано этого?
Re[3]: move-семантика
От: Vamp Россия  
Дата: 02.01.19 21:38
Оценка:
МР>А для C++ такая ошибка компиляции имела бы смысл? Или в С++ намеренно не сделано этого?
В рамках существующих языковых конструктов это сделать невозможно. (хинт — как насчёт косвенных обращений у объекту?)
Да здравствует мыло душистое и веревка пушистая.
Re[3]: move-семантика
От: Zhendos  
Дата: 03.01.19 04:54
Оценка:
Здравствуйте, Максим Рогожин, Вы писали:

МР>Здравствуйте, Zhendos, Вы писали:


Z>>Но там ошибка компиляции использование объекта после move.

МР>А для C++ такая ошибка компиляции имела бы смысл?

Конечно, она бы значительно упростила бы инварианты при написании класса,
если бы имелась бы гарантия на этапе компиляции, что после std::move будет вызван только деструктор.
А так приходится втыкать кучу assert на всякий случай.

> Или в С++ намеренно не сделано этого?


Ну разработчикам компиляторов C++ еще не всегда на вот это умеют выдавать ошибку компиляции
или хотя бы предупреждение:

void f()
{
        int *i = new int;
        delete i;
        *i = 5;
}


а вы хотите чтобы они время жизни + std::move научились анализировать.
Re: move-семантика
От: sergii.p  
Дата: 03.01.19 13:25
Оценка:
Здравствуйте, 00011011, Вы писали:

0>Никогда не понимал move-семантику.


помимо того, что уже ответили, отметил бы возможность использование кэш памяти. Для меня этот довод был основополагающим от игнорирования семантики перемещения до маниакального перемещения всего и вся.
Конструкции вида
std::vector<Object*>

могут располагаться в памяти как Бог на душу положит и обход списка в самом худшем случае будет идти минуя кэш сразу в оперативную память. При организации хранения в виде
std::vector<Object>

объекты расположены рядом и с большой вероятностью попадают в кэш (потому как загрузка из оперативной памяти происходит постранично).
Re[4]: move-семантика
От: Максим Рогожин Россия  
Дата: 03.01.19 15:48
Оценка:
Здравствуйте, Vamp, Вы писали:

V>В рамках существующих языковых конструктов это сделать невозможно. (хинт — как насчёт косвенных обращений у объекту?)

По ссылке или указателю, имеете ввиду? Ну а как в Rust с этим справились?
Re[5]: move-семантика
От: Vamp Россия  
Дата: 03.01.19 20:05
Оценка:
V>>В рамках существующих языковых конструктов это сделать невозможно. (хинт — как насчёт косвенных обращений у объекту?)
МР>По ссылке или указателю, имеете ввиду? Ну а как в Rust с этим справились?
Я не большой знаток раста, но если я правильно понимаю, строгие правила ссылок не дают возможности переместить значение и обратиться к нему по другой ссылке, а указатели, свободные от этих ограничений, никаких гарантий не дают.
Да здравствует мыло душистое и веревка пушистая.
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.