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


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

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

10 189

10 ответов:

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

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

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

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

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

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

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

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


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

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

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

  • если функция намерена изменить аргумент в качестве побочного эффекта, взять его по неконстантной ссылке.
  • если функция не изменяет свой аргумент и аргумент имеет примитивный тип, возьмите его по значению.
  • в противном случае возьмите его по ссылке 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 по умолчанию, вероятно, было бы быстрее передать значение.

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

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

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

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

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

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

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

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

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

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

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

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

передать значение для малых типов.

передать по ссылкам 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 )

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

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

  1. void amount(int total , int deposit )

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