Как стать автором
Обновить

Комментарии 27

Пожалуйста исправьте тексты на С. Вы забыли из исходника перенести признак указателя.

int example1(int value, char *message) {
......
char* msg = malloc(100);

Действительно, при копировании пропали. Спасибо, исправили)

Всё ещё нет

Как при этом будут выглядеть простые арифметические выражения?
let s = (copy x * copy x + copy y * copy y).sqrt();?

Я что-то упускаю или тут не нужно копировать? Если операторы заимствуют значения, то по идее всё будет работать без изменений.

В чем разница между копированием и перемещением?

Когда мы что-то перемещаем, то в исходном месте это что-то исчезает, правильно? А когда копируем, то нет. По-моему, разница очевидна и в запутанных пояснениях не нуждается. В расте вы не сможете использовать перемещенную переменную, потому что её больше там нет

Интересно то, что из всех этих языков последователен в плане семантики
только C. При вызове функции все значения всегда копируются. Вот так
просто.


А вы уверены, что в расте при перемещении не происходит копирование пямяти? Я имею в виду, что на стек что-то надо же положить, вот это "что-то" это не копия перемещаемого объекта? А что тогда?


Компилятор Раста довольно часто заменяет копирование на перемещение, если "скопированный" объект больше не используется.

Может быть вы имели ввиду клонирование а не копирование?

Потому как перемещение в расте реализовано через memcpy (другими словами через копирование)

Как раз clone - это "дорогое копирование", а copy - это memcpy, как и перемещение.

Что такое clone это понятно. Вопрос был в том, что значит «Компилятор Раста довольно часто заменяет копирование на перемещение»? Непонятно как эта замена выглядит и в чем ее суть?

А вы уверены, что в расте при перемещении не происходит копирование пямяти?

Всё верно, копирование происходит, но тут важна разница между условным "копированием указателя" и глубоким копированием. На примере вектора: мы или перемещаем его (копируя только указатель, размер и capacity) или копируем все данные.

Когда мы что-то перемещаем, то в исходном месте это что-то исчезает, правильно?

Нет, не правильно, конечно. Вы думаете есть какой-то способ "переместить" байты в памяти? :)

Разница именно в интерпретации копирования. "Копирование" - это копирование после которого обеими копиями можно пользоваться как эквивалентными. "Перемещение" - это копирование после которого исходным объектом пользоваться нельзя (при этом занимаемая им память осталась на месте но теперь считается свободной).

Нет, не правильно, конечно.

Разница именно в интерпретации копирования. "Копирование" - это копирование после которого обеими копиями можно пользоваться как эквивалентными. "Перемещение" - это копирование после которого исходным объектом пользоваться нельзя (при этом занимаемая им память осталась на месте но теперь считается свободной).

Он тоже самое написал.

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

Можете пояснить, а зачем нужна семантика перемещения? Лично я в большинстве случаев предполагаю, что данные «принадлежат» вызывающей функции, т.е. при передаче сложного типа (или бестипового объекта aka область памяти) удаление и/или очистка ресурса должно проводиться вне функции. Если мы передали объект в функцию, а он там умер, или указатель инвалидизировался, это side effect и так писать не стоит.

Например, есть у нас вектор данных, которые хочется обработать в другом потоке. Что делать без перемещения? Передавать указатель, потом как-то получать ответ от потока, что данные можно прибивать?.. Неудобно.


Ну или более простой пример с сеттерами: хотим заменить значение поля. Можно передать по ссылке и скопировать, а можно переместить (передать владение). Если снаружи объект больше не нужен, то экономим на копировании.

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

Я бы не сказал, что это просто вкусовщина: во многих случаях перемещение выгоднее по производительности так как мы избегаем лишнего копирования, ну или просто удобнее.

А как это в Расте фактически реализовано? memmove на адрес на стеке?

Tам memcpy, но суть в том, что компилятор не даст использовать "перемещённые" данные. Ну и я ещё раз подчеркну, что перемещение важно для данных вроде строк или векторов, которые состоят из указателя на данные и небольшого числа служебных полей. Для таких типов побитовое копирование дёшево, а глубокое — дорого.

Только вот частенько так писать приходится. В основном, потому что объекты могут не только передаваться в функцию, но и возвращаться из неё.


Вторая причина — асинхронность (в широком смысле). Иногда вызываемая функция должна работать дольше вызывающей.

Я в таких случаях пишу «недоаллокатор» из двух очередей, одна под обработку объектов в основном треде, другая под работу во вспомогательных. Соответственно поток сидит на очереди и по мере прибытия разгребает. Возможно, мне ассемблер по мозгам сильно натоптал и теперь я область памяти считаю используемой переменной до момента принудительного освобождения

По мере прибытия чего?


Рассмотрим объект "человек", с двумя полями: "имя" и "фамилия", оба поля — строковые. Строки, разумеется, хранятся как владеющие указатели, то есть всего для объекта выделено три области памяти.


Как вы его передадите в другой поток используя ваш двухочередной аллокатор?

Да, сделать move было бы удобнее, согласен. Тут бы я передавал указатели в явном виде через очередь (синхронизированную, если связь «один ко многим» или «много ко многим», или просто через кольцевой буфер, если один производитель и один потребитель), а до возврата обработки занулял, чтобы быстро упасть при попытке доступа (для простоты предположим, что асинхронная обработка каждого поля ведется в своем потоке, а потоки долгоживущие).
Я не на плюсах, а на С пишу, так что извините за код.
//Неблокирующая отправка
QueueSend(&fname_processor_q, &((string_message_t){person->fname, thd_id}));
person->fname = NULL;
QueueSend(&sname_processor_q, &((string_message_t){person->sname, thd_id}));
person->sname = NULL;
...
//Блокирующее ожидание, пока в другом потоке обновится значение по переданному указателю
NotifyTake(FNAME_FLAG);
NotifyTake(SNAME_FLAG);

//И в потоке-обработчике
for(;;){
    string_message_t tmp_message = {0};
    QueueReceive(&fname_processor_q, &tmp_message);
    ValidateFname(tmp_message.str);
    //По указателю теперь нужное значение, сообщим отправителю
   NotifyGive(tmp_message.pid, FNAME_FLAG);
}


А по поводу двухочередного аллокатора: я им пользуюсь как разделяемым буфером, например, после вызова sprintf, но до перенаправления на uart/usb/экран, тогда память помечается как свободная уже в конце обработки отправки. Если можно динамическую память, то он не особо не нужен, зато появляются другие проблемы :)

Посмотрим теперь на вот этот код внимательнее:


QueueSend(&fname_processor_q, &((string_message_t){person->fname, thd_id}));
person->fname = NULL;

Лично я тут вижу перемещение строки person->fname. То есть вы этим перемещением пользуетесь постоянно, и при этом спрашиваете зачем оно нужно?

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

Обычно этого как раз не требуется.
А если требуется — это уже не перемещение называется, а заимствование.

Зарегистрируйтесь на Хабре, чтобы оставить комментарий