Подтвердить что ты не робот

Лучше ли в С++ проходить по значению или передавать постоянную ссылку?

Лучше ли в С++ проходить по значению или передавать постоянную ссылку?

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

4b9b3361

Ответ 1

Обычно рекомендуется использовать наилучшую практику 1 для использовать pass by const ref для всех типов, за исключением встроенных типов (char, int, double и т.д.)..), для итераторов и для объектов функций (lambdas, классы, происходящие из std::*_function).

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

С С++ 11 мы получили перемещение семантики. В двух словах, перемещение семантики допускает, что в некоторых случаях объект может передаваться "по значению" без его копирования. В частности, это тот случай, когда объект, который вы проходите, является rvalue.

Само по себе перемещение объекта по-прежнему не менее дорого, чем передача по ссылке. Однако во многих случаях функция будет внутренне копировать объект в любом случае - т.е. Он будет владеть аргументом. 2

В этих ситуациях мы имеем следующий (упрощенный) компромисс:

  • Мы можем передавать объект по ссылке, а затем копировать внутри.
  • Мы можем передать объект по значению.

"Передача по значению" все еще вызывает копирование объекта, если объект не является значением rvalue. В случае rvalue объект может быть перемещен вместо этого, так что второй случай внезапно перестает "копировать, а затем перемещать", но "перемещать", затем (потенциально) перемещаться снова ".

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


Историческое примечание:

Фактически, любой современный компилятор должен уметь вычислять, когда передача по значению является дорогостоящей и неявно конвертировать вызов для использования константы ref, если это возможно.

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

Но в целом компилятор не может это определить, и появление семантики движения в С++ делает эту оптимизацию менее актуальной.


1 например. в Scott Meyers, Effective С++.

2 Это особенно часто относится к конструкторам объектов, которые могут принимать аргументы и хранить их внутри, чтобы быть частью состояния построенных объектов.

Ответ 2

Изменить: Новая статья Дейва Абрахама на cpp-next:

Хотите скорость? Перейдите по значению.


Передача по значению для структур, где дешевое копирование имеет дополнительное преимущество, что компилятор может предположить, что объекты не являются псевдонимами (это не одни и те же объекты). Используя pass-by-reference, компилятор не может всегда это предполагать. Простой пример:

foo * f;

void bar(foo g) {
    g.i = 10;
    f->i = 2;
    g.i += 5;
}

компилятор может оптимизировать его в

g.i = 15;
f->i = 2;

поскольку он знает, что f и g не имеют одного и того же местоположения. если g была ссылкой (foo &), компилятор не мог этого предположить. так как тогда g.i можно было бы сгладить через f- > я и иметь значение 7. поэтому компилятору пришлось бы повторно извлекать новое значение g.i из памяти.

Для более пратических правил, вот хороший набор правил, найденных в статье Move Constructors (рекомендуется прочитать).

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

"Примитив" означает, что в основном небольшие типы данных, длина которых несколько, и не являются полиморфными (итераторы, объектные объекты и т.д.) или дорогими для копирования. В этой статье есть еще одно правило. Идея состоит в том, что иногда хочется сделать копию (в случае, если аргумент не может быть изменен), а иногда и не нужен (в случае, если кто-то хочет использовать сам аргумент в функции, если аргумент был временным в любом случае, например). В документе подробно объясняется, как это можно сделать. В С++ 1x этот метод может быть использован изначально с поддержкой языка. До тех пор я бы пошел с вышеуказанными правилами.

Примеры. Чтобы сделать строку в верхнем регистре и вернуть версию в верхнем регистре, всегда нужно передавать по значению: каждый должен взять ее копию (невозможно напрямую изменить ссылку на константу) - так лучше сделайте ее прозрачной как возможно, вызывающему абоненту и сделать эту копию раньше, чтобы вызывающий мог оптимизировать как можно больше - как подробно описано в этой статье:

my::string uppercase(my::string s) { /* change s and return it */ }

Однако, если вам не нужно изменять параметр в любом случае, возьмите его по ссылке на const:

bool all_uppercase(my::string const& s) { 
    /* check to see whether any character is uppercase */
}

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

bool try_parse(T text, my::string &out) {
    /* try to parse, write result into out */
}

Ответ 3

Зависит от типа. Вы добавляете небольшие накладные расходы, чтобы сделать ссылку и разыменовать. Для типов с размером, равным или меньшим, чем указатели, использующие копию ctor по умолчанию, вероятно, быстрее будет проходить по значению.

Ответ 4

Как уже отмечалось, это зависит от типа. Для встроенных типов данных лучше всего перейти по значению. Даже некоторые очень маленькие структуры, такие как пара ints, могут лучше работать, передавая по значению.

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

Если вы программируете шаблоны, вы обычно вынуждены всегда передавать const ref, поскольку вы не знаете, какие типы передаются. Передача штрафов за передачу чего-то плохого по значению намного хуже, чем штрафы за прохождение встроенного -in type by const ref.

Ответ 5

Похоже, ты получил свой ответ. Передача по стоимости дорогая, но дает вам копию для работы, если вам это нужно.

Ответ 6

Как правило, переключение по ссылке const лучше. Но если вам нужно изменить аргумент функции локально, вам лучше использовать передачу по значению. Для некоторых базовых типов производительность в целом одинакова как для передачи по значению, так и по ссылке. Фактически ссылка внутренне представлена ​​указателем, поэтому вы можете ожидать, например, что для указателя обе прохождение одинаковое с точки зрения производительности, или даже передача по значению может быть быстрее из-за ненужного разыменования.

Ответ 7

Это то, что я обычно работаю при разработке интерфейса функции без шаблона:

  • Передайте значение, если функция не хочет изменять параметр и значение дешево для копирования (int, double, float, char, bool и т.д.) Обратите внимание, что std::string, std::vector, а остальные контейнеры в стандартной библиотеке НЕ)

  • Пропустить указатель const, если значение дорого копировать, и функция выполняет не хотите изменять указанное значение и NULL - это значение, которое обрабатывает функция.

  • Передача с помощью указателя non-const, если значение дорого копировать, а функция хочет изменить указанное значение и NULL - это значение, которое выполняет функция.

  • Передайте по ссылке const, когда значение будет дорого копировать, и функция не хочет изменять указанное значение, а значение NULL не будет допустимым значением, если вместо этого использовался указатель.

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

Ответ 8

Как правило, значение для типов non-class и const для классов. Если класс действительно маленький, то, вероятно, лучше пройти по значению, но разница минимальна. То, что вы действительно хотите избежать, - это передать какой-то гигантский класс по стоимости и все его дублировать - это будет иметь огромное значение, если вы передадите, скажем, std::vector с несколькими элементами в нем.

Ответ 9

Перейдите по значению для небольших типов.

Передать ссылки const для больших типов (определение большого может варьироваться между машинами). НО, в С++ 11, переходите по значению, если вы собираетесь использовать данные, поскольку вы можете использовать семантику переноса. Например:

class Person {
 public:
  Person(std::string name) : name_(std::move(name)) {}
 private:
  std::string name_;
};

Теперь вызывающий код будет делать:

Person p(std::string("Albert"));

И только один объект будет создан и перемещен непосредственно в член name_ в классе Person. Если вы перейдете по ссылке const, необходимо будет скопировать ее в name_.

Ответ 10

Простая разность: - В функции у нас есть параметр ввода и вывода, поэтому, если ваш параметр ввода и вывода одинаковый, тогда используйте вызов по ссылке else, если параметры ввода и вывода отличаются друг от друга, лучше использовать вызов по значению.

example void amount(int account , int deposit , int total )

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

ввод и вывод - это другой вызов использования vaule

  1. void amount(int total , int deposit )

общий депозит итоговая сумма