Лучше ли в C ++ передавать по значению или передавать по константной ссылке? - PullRequest
199 голосов
/ 07 ноября 2008

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

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

Ответы [ 10 ]

187 голосов
/ 07 ноября 2008

Как правило, рекомендуется использовать лучшие практики 1 до использовать проход по константу ref для всех типов , кроме встроенных типов (char, int, double и т. Д.), Для итераторов и функциональных объектов (лямбда-выражения, классы, производные от std::*_function).

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

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

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

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

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

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

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


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

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

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

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


1 Например. в Скотт Мейерс, Эффективный C ++ .

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

94 голосов
/ 07 ноября 2008

Редактировать: Новая статья Дэйва Абрахамса на cpp-next:

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


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

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-> i и должно иметь значение 7. Таким образом, компилятор должен будет повторно извлечь новое значение g.i из памяти.

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

  • Если функция намеревается изменить аргумент в качестве побочного эффекта, используйте его по неконстантной ссылке.
  • Если функция не изменяет свой аргумент и аргумент имеет примитивный тип, принимайте его по значению.
  • В противном случае принимайте его по ссылке, за исключением следующих случаев.
    • Если функции в любом случае потребуется скопировать ссылку на 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 */
}
12 голосов
/ 07 ноября 2008

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

8 голосов
/ 07 ноября 2008

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

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

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

5 голосов
/ 11 июня 2015

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

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

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

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

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

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

5 голосов
/ 07 ноября 2008

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

4 голосов
/ 07 ноября 2008

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

1 голос
/ 29 октября 2013

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

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

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

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

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

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

1 голос
/ 07 ноября 2008

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

0 голосов
/ 05 января 2015

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

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

входной параметр: счет, депозит выходной параметр: всего

вход и выход различаются вызовом по vaule

  1. void amount(int total , int deposit )

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

...