2009-10-14 4 views
15

Если у вас есть функция, которая принимает, например, int, каков наилучший способ объявления функции и почему она лучше, чем другая?«const T & arg» vs. «T arg»

void myFunction (const int &myArgument); 

или

void myFunction (int myArgument); 
+0

Смотрите также [эти правила эмпирического] (http://stackoverflow.com/a/2139254/140719) для передачи аргументов функции. – sbi

ответ

36

Использование const T & arg если sizeof(T)>sizeof(void*) и использовать T arg если sizeof(T) <= sizeof(void*)

+11

Это неверная идентификация. Я бы не выбрал 'const double & t' over' double t', но ваше предложение предлагает сделать это на 32-битных системах. –

+6

Да, возможно, double является исключением. –

+1

Есть и другие исключения. Иногда вы хотите изменить локальную копию параметра. Тогда, взяв его ref-to-const, фактически предотвратит оптимизацию компилятора, например, копии. См. Ссылку на блог Дейва, который я дал в своем ответе. – sellibitze

3

Обычно для встроенных типов можно просто передать по значению. Они маленькие.

Для пользовательских типов (или шаблонов, когда вы не собираетесь передавать) предпочитаете const &. Размер ссылки, вероятно, меньше размера этого типа. И это не приведет к дополнительной копии (без вызова конструктора копирования).

+0

Абсолютно - стоимость копирования (и последующего уничтожения этой копии) являются ключевыми. Очень маленький класс не-POD может иметь дорогостоящую конструкцию и разрушение копии. например класс c_Binary_Tree может иметь sizeof (void *) - один элемент, указывающий на корневой узел, но конструктор копирования может скопировать все двоичное дерево. std :: map и т. д., вероятно, близки к этому, хотя экземпляры (в типичных реализациях), вероятно, содержат указатели на начальный и конечный узлы, а также на корень, делая их 3 * sizeof (void *). – Steve314

+0

Кстати, один метод * должен * иметь параметр ссылки const, независимо от того, насколько дешево построено копирование - сам конструктор копирования.В противном случае - ну, параметр - это копия, поэтому вам нужно вызвать конструктор копирования для его настройки, а конструктору копирования нужна копия в качестве параметра, поэтому вам понадобится другой вызов конструктора копирования, чтобы установить его, поэтому вам нужна другая копия поскольку этот параметр вызывает, поэтому вы ... – Steve314

+0

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

-1

Либо работает отлично. Не теряйте время, беспокоясь об этом.

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

+2

Если вы передаете вектор с несколькими мегабайтами (или другим контейнером), то передача по ссылке будет иметь меньшую разницу. Сам вектор крошечный (sizeof (void *) * 3, обычно), но copy-ctor убьет производительность. –

0

Ну, разница между этими двумя значениями не имеет большого значения для ints.

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

В обеих случаях вы видите это в вызывающих

myFunct(item); 

Для абонента, элемент не будет изменен myFunct, но передача по ссылке не будет нести расходы на создание копии.

Существует очень хороший ответ на аналогичный вопрос по крайней Pass by Reference/Value in C++

1

Для простых типов, таких как INT, двойной и символ *, то имеет смысл передать его по значению. Для более сложных типов я использую const T &, если нет конкретной причины не делать этого.

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

19

Они делают разные вещи. const T& делает функцию ссылкой на переменную. С другой стороны, T argназовет копировального аппарата объекта и передает копию . Если конструктор копирования недоступен (например,это private), T arg не будет работать:

class Demo { 
    public: Demo() {} 
    private: Demo(const Demo& t) { } 
}; 

void foo(Demo t) { } 

int main() { 
    Demo t; 
    foo(t); // error: cannot copy `t`. 
    return 0; 
} 

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

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

+0

Вы можете прочитать эту дискуссию: http://stackoverflow.com/questions/1561007/c-error-when-have-private-copy-ctor-with-public-assignment-operator иногда (а именно с временными объектами), имеющий конструктор частной копии, может не выполнять даже вызовы функций с const & (по крайней мере, для C++ 98). –

+0

Орен: Это правда, но это побочный вопрос. Это не следствие вызовов функций. Как вы сказали, проблема в том, что у вас нет ссылок на временные. –

+1

Термин «POD», по-видимому, используется неправильно. POD никак не связан с малыми типами. Значения POD могут быть сколь угодно большими. Массив из 10000 'int' - это POD. Автор, вероятно, имел в виду сказать «скалярные типы» или «базовые типы» вместо «типов POD». – AnT

2

Ну, да ... другие ответы об эффективности верны. Но здесь есть что-то еще, что важно - передача класса по значению создает копию и, следовательно, вызывает конструктор копирования. Если вы делаете причудливые вещи, это еще одна причина использовать ссылки.

+1

Зависит от фактического типа и вашего компилятора. Является ли это тривиально гибким типом типа POD? Может ли ваш компилятор выдавать копии? «... вызывает копию ctor ...» не является гарантией. В некоторых случаях его можно оптимизировать. – sellibitze

1

Это не будет иметь никакого значения для int, поскольку, когда вы используете ссылку, адрес памяти еще должен быть передан, а адрес памяти (void *) обычно имеет размер целого.

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

4

Вопреки популярным и давним убеждениям, переход по ссылке const не обязательно быстрее, даже когда вы проходите большой объект. Возможно, вы захотите прочитать Дейва Абрахама последние article по этому вопросу.

Редактировать: (в основном, в ответ на комментарии Джеффа Харди): Это правда, что переход по ссылке const является, вероятно, «самой безопасной» альтернативой при наибольшем числе обстоятельств, но это не значит, что всегда лучше всего делать. Но, чтобы понять, что здесь обсуждается, вам действительно нужно внимательно прочитать всю статью Дэйва, поскольку она довольно техническая, а аргументация ее выводов не всегда интуитивно очевидна (и вам нужно понять, что нужно делать разумные выборы).

+1

Это правда * если вы все равно сделаете копию *. Если нет, вам все равно лучше передавать по ссылке, потому что передача lvalue создаст копию, которая вам действительно не нужна. –

+1

Эта статья посвящена _returning_ объектам копией и возврату их в параметр _non-const reference_ в _C++ 1x_. Это, очевидно, здесь не применимо. – sbi

+2

Да, конечно, есть ограничения на это - вот почему 1) я сказал «обязательно» и 2) направил людей к статье для получения полной информации ... –

-1

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

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

Мораль: Напишите свои конструкторы копирования.

+2

Если ваш класс освобождает память в деструкторе и имеет конструктор-конструктор по умолчанию, то он сломан. Это правда, что это вызывает проблемы с передачей по значению, но также вызывает всевозможные проблемы в другом месте. Решение состоит в том, чтобы не пропускать сломанные классы по значению, решение заключается не в том, чтобы писать сломанные классы :-) –

+0

Позвольте мне перефразировать и изменить вашу мораль: попробуйте разработать свои классы, чтобы созданная компилятором copy/assign/dtor выполняла Правильная вещь в большинстве случаев. Предоставляйте пользовательские функции как можно меньше классов. Затем вы применили RAII правильно. – sellibitze

0

Разница между ними заключается в том, что вы передаете int (который копируется), а один использует существующий int. Так как это ссылка const, она не изменяется, поэтому она работает практически одинаково. Большая разница здесь в том, что функция может изменять значение int локально, но не ссылку const. (Я полагаю, что идиот мог бы сделать то же самое с const_cast<> или, по крайней мере, попытаться.) Для более крупных объектов я могу думать о двух отличиях.

Во-первых, некоторые объекты просто не могут быть скопированы, auto_ptr<> s и объекты, содержащие их, являются очевидным примером.

Во-вторых, для больших и сложных объектов это быстрее передать по ссылке const ссылку, чем скопировать.Это, как правило, не очень важно, но передача объектов по const является полезной привычкой.

11

взято с Move constructors. Мне нравится Простые правила

  1. Если функция намеревается изменить аргумент в качестве побочного эффекта, возьмите его ссылочный/указатель неконстантного объекта. Пример:

    void Transmogrify(Widget& toChange); 
    void Increment(int* pToBump); 
    
  2. Если функция не изменяет свой аргумент и аргумент примитивного типа, взять его значение. Пример:

    double Cube(double value); 
    
  3. В противном случае

    3,1. Если функция всегда делает копию своего аргумента внутри, возьмите ее по значению.

    3.2. Если функция никогда не делает копию своего аргумента, возьмите ее ссылкой на const.

    3.3. Добавил (а) меня:: Если функция иногда делает копию, то решите, что происходит с кишкой: если копия выполняется почти всегда, тогда возьмите по значению. Если копия выполняется половину времени, перейдите по безопасному пути и возьмите ссылку на const.

В вашем случае вы должны взять int по значению, потому что вы не намерены изменять аргумент, а аргумент имеет примитивный тип. Я думаю о «примитивном типе» как о неклассическом типе или типе без пользовательского конструктора копирования и где sizeof(T) составляет всего пару байтов.

+0

3.3. Если функция иногда делает копию своего аргумента ...? –

+0

Я не включил это, потому что это использует его фреймворк (для эмуляции «конструкторов перемещения») :) Если иногда требуется копия, я бы сказал, чтобы решить, что происходит с кишкой (например, как часто делается копия внутри?) это?) Например, для оператора =, который имеет постоянную свопинг (copy/swap idiom), я бы взял его на значение, потому что я бы редко его не копировал (самозапуск). –

+0

(см. Отличный ответ Джерри Коффина и связанная статья для того, почему принятие оператора = arg по значению - это хорошо) –

2

Ссылка на const T не стоит усилий на печать в случае таких скалярных типов, как int, double и т. Д. Эмпирическое правило состоит в том, что типы классов должны приниматься посредством ref-to-const. Но для итераторов (которые могут быть типами классов) мы часто делаем исключение.

В общем коде вы должны, вероятно, написать «T const &» в большинстве случаев, чтобы быть в безопасности. Также есть boost's call traits, который вы можете использовать для выбора наиболее перспективного типа прохождения параметра. Насколько я могу судить, он в основном использует ref-to-const для типов классов и pass-by-value для скалярных типов.

Но есть также ситуации, когда вы можете принять параметры по значению, независимо от того, насколько дорого может быть создание копии. См. Статью Дейва "Want Speed? Use pass by value!".

6

Существует популярный совет, в котором говорится, что метод передачи («по значению» и «по ссылке const») должен выбираться в зависимости от фактического размера типа, который вы собираетесь передать. Даже в этой дискуссии у вас есть ответ, обозначенный как «правильный», который предлагает именно это.

В действительности, ваше решение по размеру типа не только неверно, это майор и довольно вопиющая ошибка дизайна, открывающая серьезное отсутствие интуиции/понимание хороших практик программирования.

Решения, основанные на фактических размерах объектов, зависящих от реализации, должны быть предоставлены компилятору как можно чаще. Попытка «приспособить» ваш код к этим размерам с помощью жесткого кодирования метода прохождения является полностью контрпродуктивной тратой усилий в 99 случаях из 100.(Да, это правда, что в случае языка C++ у компилятора нет достаточной свободы для использования этих методов взаимозаменяемо - на самом деле они не являются взаимозаменяемыми в C++ в общем случае. Хотя, при необходимости, правильный размер [ полуавтоматическая передача метаоса может быть реализована посредством метапрограммирования шаблонов, но это совсем другая история).

Гораздо более значимым критерием для выбора метода прохождения, когда вы пишете код «вручную» может звучать следующим образом:

  1. Предпочитаете пройти «по значению», когда вы передаете атомное, унитарная , неделимый объект, такой как одно неагрегатное значение любого типа - число, указатель, итератор. Обратите внимание, что, например, итераторы являются унитарными значениями на логическом уровне. Поэтому, предпочитайте передавать итераторы по значению, независимо от того, превышает ли их фактический размер sizeof (void *). (Реализация STL делает именно это, BTW).

  2. Предпочитает передавать «по ссылке const», когда вы передаете совокупное, составное значение любого типа. т. е. значение, которое на логическом уровне явно выражено «составной» природой, даже если его размер не превышает sizeof (void *).

Разделение между ними не всегда ясно, но, как всегда, со всеми такими рекомендациями. Более того, разделение на «атомарные» и «сложные» объекты может зависеть от особенностей вашего дизайна, поэтому решение может фактически отличаться от одного проекта к другому.

Обратите внимание, что это правило может принимать решения, отличные от решений якобы «правильного» метода на основе размера, упомянутого в этом обсуждении.

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

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


В C++ 11 введения семантики хода на язык произвел заметный сдвиг в относительных приоритетах различных методов параметров в обходе. При определенных обстоятельствах это может стать вполне возможным передавать даже сложные объекты по значению

Should all/most setter functions in C++11 be written as function templates accepting universal references?

+0

«... выявление серьезного недостатка интуиции/понимания хороших практик программирования». Взгляните на «JSF AV ++», а затем на правило проверки 116. Этот стандарт был написан военными США для их совместного ударного бойца с участием некоторых крупных имен. Я не уверен, что они считают, что им не хватает интуиции/понимания хороших методов программирования !!! –

+2

@ Рихард Корден: Совершенно безотносительно, не хватало ли этого понимания или нет, так как я уверен, что это не было приоритетом. Обоснование, которое они использовали, скорее всего, полностью ортогонально к любой «хорошей практике программирования». Цель их стандарта состояла в том, чтобы наилучшим образом использовать строго определенный набор инструментов в весьма специфической (или, если можно так сказать, экзотической) среде, что очень мало связано с общим программированием на C++ на уровне приложений и хорошими практиками, которые примените там. – AnT

+0

... Еще больше читаю документ, я в основном вижу, что я ожидал увидеть в военном документе. Это высокоспециализированный стандарт кодирования, специально предназначенный для набора конкретных, довольно экзотических обстоятельств. Довольно много представленных здесь «правил» содержат очень сильные «анти-правила» для общего программирования на С ++ в «нормальных» обстоятельствах. – AnT

Смежные вопросы