#c
#c
Вопрос:
Я читал о семантике перемещения и считаю, что действительно эффективный обмен заключается в следующем (для простоты мы пока оставим все как int вместо использования дженериков):
void swap(intamp; a, intamp; b) {
int tmp = std::move(a);
a = std::move(b);
b = std::move(tmp);
}
Это правильная реализация подкачки (для целых чисел), верно? Строка 2 не создает дополнительный элемент в ОЗУ tmp
, верно?
Мой другой вопрос: выполняет ли этот же код, использующий ссылки вместо std::move, так же хорошо, как и задание (например, не создавая дополнительную переменную для tmp
), как пример семантики перемещения выше?
void swap(intamp; a, intamp; b) {
int tmp = amp;a;
a = amp;b;
b = amp;tmp;
}
Создает ли приведенный выше код дополнительную переменную для tmp
? Правильно ли он меняет a
местами и b
?
Комментарии:
1. Нет никакой разницы между перемещением и копированием для типов POD. Лучшим тестовым примером было бы использование не-POD подвижного типа, например
std::string
. И да, строка 2 выделяет память дляtmp
, а во втором примереtmp
всегда является копиейa
(и, кстати, ваше использованиеamp;
внутри функции неверно).2. Ссылка — это указатель с добавленными дополнительными ограничениями (не может быть переназначен, не может создать их массив). Почему бы просто не передать указатели и не использовать
<T>* tmp = a; *a = b; *b = tmp;
? Если не просто замена указателей, вам нужно создать полный дополнительный объект как tmp, чтобы хранить3. @Dave — » Ссылка — это указатель » — не с точки зрения стандарта, это не так. Хотя использование указателя — это то, сколько часто используемых компиляторов реализуют ссылки под капотом.
4.
int tmp = amp;a;
ошибка (указатель не может быть присвоен целому числу без приведения)
Ответ №1:
Встроенные типы не имеют конструктора перемещения или оператора присваивания перемещения, поэтому ваша первая версия действительно ничем не отличается от
void swap(intamp; a, intamp; b)
{
int tmp = a;
a = b;
b = tmp;
}
Ваша вторая версия не будет компилироваться. Однако, скорее всего, вы просто хотели написать то же самое, что и моя версия выше. Обе версии вводят временную переменную с именем tmp
для сохранения значения, которое необходимо присвоить одному элементу обмена, в то время как у другого изменяется значение.
Не работайте в предположении, что каждая конструкция в вашем коде, такая как переменная, имеет ровно одну соответствующую конструкцию в сгенерированном машинном коде, такую как «дополнительный элемент в оперативной памяти», что это всегда приведет. Современные компиляторы работают не так. Задача компилятора не в том, чтобы брать каждую строку исходного кода и выполнять замену 1: 1 определенной последовательностью машинных инструкций. Задача компилятора состоит в том, чтобы взять всю программу, которую вы описали на языке C , и сгенерировать эквивалентную программу, например, в машинном коде, которая при выполнении будет вести себя так, чтобы ее поведение было неотличимо от поведения программы, описанной в вашем исходном коде C .
Если вы посмотрите на созданную сборку, вы увидите, что в случае замены простых целых чисел ни один здравомыслящий компилятор не переместит эту временную переменную в память, а будет использовать регистры для замены значений. Вы можете доверять своему компилятору, чтобы знать, какой наиболее эффективный способ поменять местами два целых числа в данной целевой архитектуре. Если вы не можете доверять своему компилятору, чтобы знать это, тогда вам следует искать лучший компилятор…
На самом деле, трудно сказать много о том, насколько «эффективна» эта swap
функция, просто посмотрев на код, который она генерирует изолированно. На практике такая функция, как указано swap
выше, обычно должна быть полностью оптимизирована. Единственный след, который он обычно оставляет в машинном коде, — это эффект, который он оказывает на поток данных (пример здесь ), но никогда не будет фактической инструкции вызова для вызова отдельного фрагмента машинного кода для выполнения замены. Действительно важная вещь, которую нужно сделать, когда дело доходит до оптимизации swap
, — убедиться, что компилятор действительно может оптимизировать его (что обычно сводится к тому, чтобы убедиться, что его определение известно везде, где оно используется).
Мораль истории: сосредоточьтесь не на описании того, как, по вашему мнению, должен работать машинный код, а на выражении ваших намерений таким образом, чтобы это было доступно для чтения как вам, так и компилятору, и, в первую очередь, таким образом, чтобы правильно описывать предполагаемое поведение на основе правил языка, а неправила целевого оборудования. Никогда не полагайтесь на то, что, по вашему мнению, соответствует определенной языковой конструкции на машинном уровне. Полагайтесь на то, что язык C говорит о поведении, которое вызывает определенная языковая конструкция. Современные компиляторы C очень хорошо переводят сложный код C в очень экономичный и эффективный машинный код, который выполняет именно то, что было выражено в C . Однако они делают это, агрессивно используя тот факт, что поведение, которое было выражено, также является единственным поведением, которое должно соблюдаться. В результате современные компиляторы C очень плохо генерируют машинный код, который не соответствует тому, что было написано, а просто задумано в момент написания…
Ответ №2:
Обмен ссылками будет выглядеть так:
void swap(intamp; a, intamp; b) {
int tmp = a;
a = b;
b = tmp;
}
Вы ставите только amp;
при объявлении, что это ссылка, но не при ее использовании. Ссылки можно использовать как обычные переменные.
Поскольку вы работаете с целыми числами, этот обмен так же эффективен, как и обмен с std::move
помощью . Семантика перемещения дает преимущество только при работе с классами, которые владеют ресурсами.
Например, вектор владеет указателем на массив, поэтому перемещение вектора намного эффективнее, чем копирование вектора, потому что при копировании вектора вам нужно скопировать все данные во внутренний массив вектора. С другой стороны, если вы перемещаете вектор, ему не нужно копировать внутренний массив. Ему нужно только скопировать указатель на массив, и это намного, намного быстрее.