Комментарии 27
Пожалуйста исправьте тексты на С. Вы забыли из исходника перенести признак указателя.
int example1(int value, char *message) {
......
char* msg = malloc(100);
Как при этом будут выглядеть простые арифметические выражения?
let s = (copy x * copy x + copy y * copy y).sqrt();
?
В чем разница между копированием и перемещением?
Когда мы что-то перемещаем, то в исходном месте это что-то исчезает, правильно? А когда копируем, то нет. По-моему, разница очевидна и в запутанных пояснениях не нуждается. В расте вы не сможете использовать перемещенную переменную, потому что её больше там нет
Интересно то, что из всех этих языков последователен в плане семантики
только C. При вызове функции все значения всегда копируются. Вот так
просто.
А вы уверены, что в расте при перемещении не происходит копирование пямяти? Я имею в виду, что на стек что-то надо же положить, вот это "что-то" это не копия перемещаемого объекта? А что тогда?
Компилятор Раста довольно часто заменяет копирование на перемещение, если "скопированный" объект больше не используется.
А вы уверены, что в расте при перемещении не происходит копирование пямяти?
Всё верно, копирование происходит, но тут важна разница между условным "копированием указателя" и глубоким копированием. На примере вектора: мы или перемещаем его (копируя только указатель, размер и capacity) или копируем все данные.
Когда мы что-то перемещаем, то в исходном месте это что-то исчезает, правильно?
Нет, не правильно, конечно. Вы думаете есть какой-то способ "переместить" байты в памяти? :)
Разница именно в интерпретации копирования. "Копирование" - это копирование после которого обеими копиями можно пользоваться как эквивалентными. "Перемещение" - это копирование после которого исходным объектом пользоваться нельзя (при этом занимаемая им память осталась на месте но теперь считается свободной).
Нет, не правильно, конечно.
Разница именно в интерпретации копирования. "Копирование" - это копирование после которого обеими копиями можно пользоваться как эквивалентными. "Перемещение" - это копирование после которого исходным объектом пользоваться нельзя (при этом занимаемая им память осталась на месте но теперь считается свободной).
Он тоже самое написал.
Понимаю, что дискутировать с автором нет смысла так как это перевод, но явноe перемещение будет слишком зашумлять код, хотя и добавляет "консистентности". Хотя когда-то тоже задавался схожим вопросом относительно "единообразия" передачи по значению или по константной ссылке. Примитивы принято передавать по значению независимо от семантики функции, но порой возникают граничные случаи когда задумываешься достаточно ли большой тип, что уже пора передавать по ссылке или нет. Предложенный гипотетически язык это в принципе решает.
Например, есть у нас вектор данных, которые хочется обработать в другом потоке. Что делать без перемещения? Передавать указатель, потом как-то получать ответ от потока, что данные можно прибивать?.. Неудобно.
Ну или более простой пример с сеттерами: хотим заменить значение поля. Можно передать по ссылке и скопировать, а можно переместить (передать владение). Если снаружи объект больше не нужен, то экономим на копировании.
Например, есть у нас вектор данных, которые хочется обработать в другом потоке. Что делать без перемещения?Я в таких случаях или передаю передаю копию, а оригинал прибиваю руками, или да, указатель, все равно его надо маркировать как занятый, на случай если делим массив объектов между потоками. Я понимаю, что по сути вкусовщина и сахар, просто непривычно.
Я бы не сказал, что это просто вкусовщина: во многих случаях перемещение выгоднее по производительности так как мы избегаем лишнего копирования, ну или просто удобнее.
Tам memcpy, но суть в том, что компилятор не даст использовать "перемещённые" данные. Ну и я ещё раз подчеркну, что перемещение важно для данных вроде строк или векторов, которые состоят из указателя на данные и небольшого числа служебных полей. Для таких типов побитовое копирование дёшево, а глубокое — дорого.
Только вот частенько так писать приходится. В основном, потому что объекты могут не только передаваться в функцию, но и возвращаться из неё.
Вторая причина — асинхронность (в широком смысле). Иногда вызываемая функция должна работать дольше вызывающей.
По мере прибытия чего?
Рассмотрим объект "человек", с двумя полями: "имя" и "фамилия", оба поля — строковые. Строки, разумеется, хранятся как владеющие указатели, то есть всего для объекта выделено три области памяти.
Как вы его передадите в другой поток используя ваш двухочередной аллокатор?
//Неблокирующая отправка
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
. То есть вы этим перемещением пользуетесь постоянно, и при этом спрашиваете зачем оно нужно?
Немного о семантиках перемещения, копирования и заимствования