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

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

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

10 ответов


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

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

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

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

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

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

"Pass by value" по-прежнему вызывает копирование объекта, если объект не является rvalue. В в случае rvalue объект может быть перемещен, так что второй случай внезапно перестает быть "копировать, затем перемещать", но "перемещать, затем (потенциально) перемещать снова".

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


историческая справка:

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

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

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


1 Е. Г. в Скотт Мейерс, Эффективный C++.

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


Edit: новая статья Дэйва Абрахамса о cpp-next:

нужна скорость? Проходите по значению.


Pass by value для структур, где копирование дешево, имеет дополнительное преимущество, что компилятор может предположить, что объекты не являются псевдонимами (не являются теми же объектами). Используя 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 &), компилятор не мог этого предположить. с Г. затем я могу быть алиасирован f - >i и должен иметь значение 7. таким образом, компилятору придется повторно извлечь новое значение g.я по памяти.

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

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

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

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

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 */
}

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


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

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

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


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


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


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

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

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

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

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

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


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


Pass by value для небольших типов.

передайте ссылки const для больших типов (определение big может варьироваться между машинами), но в C++11 передайте значение, если вы собираетесь использовать данные, так как вы можете использовать семантику перемещения. Например:

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

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

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

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


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

пример void amount(int account , int deposit , int total )

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

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

  1. void amount(int total , int deposit )

вход общая депозит вывод итого