it-swarm.com.ru

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

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

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

199
Matt Pascoe

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

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

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

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

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

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

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

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


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

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

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

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


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

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

187
Konrad Rudolph

Редактировать: Новая статья Дэйва Абрахамса на 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 */
}
94
Johannes Schaub - litb

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

12
Lou Franco

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

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

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

8
Torlack

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

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

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

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

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

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

5
Martin G

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

5
GeekyMonkey

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

4
sergtk

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

1
Peter

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

Передача по константным ссылкам для больших типов (определение больших может варьироваться в зависимости от машины), НО в 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
Germán Diago