Параметры функции С++: используйте ссылку или указатель (а затем разыменовать)?

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

Вот несколько примеров:


bool MyFunc1(int * val1, int * val2)
{
    *val1 = 5;
    *val2 = 10;
    return true;
}

bool MyFunc2(int &val1, int &val2)
{
    val1 = 5;
    val2 = 10;
    return true;
}

Я лично предпочитаю переход по ссылке в качестве стиля, но одна версия лучше (в терминах технологических циклов), чем другой?

Ответы

Ответ 1

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

Ответ 2

С точки зрения производительности, это, вероятно, не имеет значения. Другие уже ответили на это.

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

Ответ 3

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

Ответ 4

Это будет отклонено с момента старта Skool, но я часто предпочитаю указатели, так как проще просто взглянуть на код и посмотреть, могут ли изменяться мои объекты, которые я передаю функции, особенно если они простые типы данных, такие как int и плавать.

Ответ 5

Существуют различные рекомендации по использованию ссылочных или указательных параметров, адаптированных к различным требованиям. На мой взгляд, наиболее значимым правилом, которое должно применяться в общей разработке на С++, является следующее:

  • Используйте ссылочные параметры при перегрузке операторов. (В этом случае у вас на самом деле нет выбора. Это то, на что были сделаны ссылки в первую очередь.)

  • Используйте const-reference для композитных (то есть логически "больших" ) входных параметров. I. входные параметры должны передаваться либо по значению ( "атомные" значения), либо по const-reference ( "совокупные" значения). Используйте указатели для выходных параметров и параметров ввода-вывода. Не используйте ссылки для выходных параметров.

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

Следуя приведенному выше соглашению, вы сможете увидеть в точке вызова, может ли функция изменять один из своих аргументов: потенциально измененные аргументы будут переданы с явным & или уже существующими указателями.

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

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

Ответ 6

Здесь разница в сгенерированной сборке с g++. a.cpp - указатели, b.cpp - ссылки.

$ g++ -S a.cpp

$ g++ -S b.cpp

$ diff a.S b.S
1c1
<       .file   "a.cpp"
---
>       .file   "b.cpp"
4,6c4,6
< .globl __Z7MyFunc1PiS_
<       .def    __Z7MyFunc1PiS_;        .scl    2;      .type   32;     .endef
< __Z7MyFunc1PiS_:
---
> .globl __Z7MyFunc1RiS_
>       .def    __Z7MyFunc1RiS_;        .scl    2;      .type   32;     .endef
> __Z7MyFunc1RiS_:

Просто имя функции немного отличается; содержимое идентично. У меня были идентичные результаты, когда я использовал g++ -O3.

Ответ 7

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

Однако, я думаю, что первая функция превосходит другую с точки зрения удобочитаемости, потому что вызов типа

f( &a, &b );

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

f( a, b );

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

Ответ 8

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

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

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

В основном это происходит из:

  • Создать объект
  • Передать как неконстантную ссылку

To:

  • Создать объект
  • Получить адрес объекта
  • Пропустить адрес
  • Убедитесь, что адрес указывает на null
  • Разница возвращается к неконстантной ссылке для использования во всей функции

Пока компилятор будет анализировать весь этот мусор, читатель кода не будет. Это будет дополнительный код для рассмотрения при расширении функций или при разработке кода. Также записывается больше кода, что увеличивает количество строк, которые могут содержать ошибку. И поскольку это указывает на то, что ошибки более склонны быть изворотливыми ошибками поведения undefined.

Он также призывает более слабых программистов написать такой код:

int* a = new int(4); // Don't understand why this has to be a pointer
int* b = new int(5); // Don't understand why this has to be a pointer
MyFunc2(a, b);
int& a_r = *a;
int& b_r = *b;

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

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

MyFunc2(/*&*/a, /*&*/b)

Вся "читаемость" амперсанда, ни один из связанных рисков указателя.

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

Ответ 9

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

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

Ответ 10

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

Ответ 11

Если вам нужно сделать что-то вроде ниже, чтобы помнить, что "a" будет изменено на функцию. Что мешает вам инвертировать параметры, имеющие один и тот же тип, или сделать какую-то неприятную ошибку, когда вам нужно вызвать какую-то функцию, вы должны сохранить прототип в своей памяти или использовать некоторую среду IDE, отображающую ее во всплывающей подсказке. Нет никаких оправданий для этого, мне не нравятся ссылки, потому что я не могу знать, изменит ли функция его, не является допустимым аргументом.

MyFunc2(/*&*/a, /*&*/b)