Эффективный обмен с использованием ссылок?

Я читал о семантике перемещения и считаю, что действительно эффективным свопом является следующее (сейчас мы будем хранить все как int вместо использования дженериков для простоты):

void swap(int& a, int& b) { 
    int tmp = std::move(a);
    a = std::move(b); 
    b = std::move(tmp);
}

Это правильная реализация подкачки (для целых чисел), верно? Строка 2 не создает дополнительный элемент в оперативной памяти для tmp, верно?

Другой мой вопрос: делает ли этот же код, использующий ссылки вместо std::move, такую ​​же хорошую работу (например, не создавая дополнительную переменную для tmp), как пример семантики перемещения выше?

void swap(int& a, int& b) { 
    int tmp = &a;
    a = &b; 
    b = &tmp;
}

Создает ли приведенный выше код дополнительную переменную для tmp? Правильно ли поменять местами a и b?

Нет никакой разницы между перемещением и копированием для типов POD. Лучшим тестовым примером было бы использование подвижного типа без POD, например std::string. И да, строка 2 выделяет память для tmp, а во втором примере tmp всегда является копией a (и, кстати, вы используете & внутри функции неправильно).

Remy Lebeau 10.04.2019 00:07

Ссылка — это указатель с добавленными дополнительными ограничениями (не может быть переназначен, не может создать их массив). Почему бы просто не передавать указатели и не использовать <T>* tmp = a; *a = b; *b = tmp;? Если не просто поменять местами указатели, вам нужно создать полный дополнительный объект как tmp, чтобы удерживать

Dave S 10.04.2019 00:08

@Dave - «Ссылка — это указатель» - не с точки зрения стандарта, это не так. Хотя использование указателя - это то, сколько часто используемых компиляторов воплощать в жизнь ссылается под капотом.

Remy Lebeau 10.04.2019 00:09
int tmp = &a; — ошибка (указатель нельзя присвоить целому без приведения)
M.M 10.04.2019 00:11
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
4
181
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Обмен с использованием ссылок будет выглядеть так:

void swap(int& a, int& b) {
    int tmp = a;
    a = b;
    b = tmp;
}

Вы ставите & только при объявлении ссылки, но не при ее использовании. Ссылки можно использовать как обычные переменные.

Поскольку вы работаете с целыми числами, этот обмен так же эффективен, как и обмен с std::move. Семантика перемещения дает преимущество только тогда, когда вы работаете с классами, которые владеют ресурсами.

Например, вектору принадлежит указатель на массив, поэтому перемещение вектора намного эффективнее, чем копирование вектора, потому что при копировании вектора необходимо скопировать все данные во внутреннем массиве вектора. С другой стороны, если вы перемещаете вектор, ему не нужно копировать внутренний массив. Ему нужно только скопировать указатель на массив, и это намного, намного быстрее.

Ответ принят как подходящий

У встроенных типов нет конструктора перемещения или оператора присваивания перемещения, поэтому ваша первая версия ничем не отличается от предыдущей.

void swap(int& a, int& b)
{ 
    int tmp = a;
    a = b; 
    b = tmp;
}

Ваша вторая версия не будет компилироваться. Однако, скорее всего, вы просто хотели написать то же самое, что и моя версия выше. Обе версии вводят временную переменную с именем tmp для хранения значения, которое необходимо присвоить одному swappee, в то время как значение другого изменяется.

Не полагайтесь на то, что каждая конструкция в вашем коде, такая как переменная, имеет только одну соответствующую конструкцию в сгенерированном машинном коде, такую ​​как «дополнительный элемент в ОЗУ», к которому она всегда будет приводить. Это не так. современные компиляторы работают. Работа компилятора заключается не в том, чтобы брать каждую строку исходного кода и выполнять замену 1:1 определенной последовательностью машинных инструкций. Задача компилятора состоит в том, чтобы взять всю программу, которую вы описали на языке C++, и сгенерировать программу эквивалент, например, в машинном коде, которая при выполнении будет вести себя неотличимо от поведения программы. который описан в вашем исходном коде C++.

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

На самом деле, трудно сказать, насколько «эффективна» эта функция swap, просто взглянув на код, который она генерирует изолированно. На практике такая функция, как swap выше, обычно должна быть полностью оптимизирована. Единственный след, который он обычно оставляет в машинном коде, — это эффект, который он оказывает на поток данных (пример здесь), но никогда не будет фактической инструкции вызова для вызова отдельного фрагмента машинного кода для выполнения подкачки. Когда дело доходит до оптимизации В самом деле, важно убедиться, что компилятор могу действительно оптимизирует его (что обычно сводится к тому, чтобы убедиться, что его определение известно везде, где оно используется).

Мораль этой истории: сосредоточьтесь не на описании того, как, по вашему мнению, должен работать машинный код, а на выражении ваших намерений таким образом, который будет понятен как вам, так и компилятору, и, в первую очередь, таким образом, чтобы правильно описывал задуманное. поведение на основе правил языка, а не правил целевого оборудования. Никогда не полагайтесь на то, что вы думать отображает определенная языковая конструкция на машинном уровне. Полагайтесь на то, что язык C++ говорит о поведении вызывает определенная языковая конструкция. Современные компиляторы C++ отлично переводят сложный код C++ в очень компактный и эффективный машинный код, который делает точно то, что было выражено в C++. Однако они делают это, агрессивно используя тот факт, что выраженное поведение также является поведением единственный, которое необходимо уважать. В результате современные компиляторы C++ очень плохой генерируют машинный код, который не делает того, что было написано, а просто думал в момент написания...

Другие вопросы по теме