C ++ «Лучший» метод передачи параметров - PullRequest
7 голосов
/ 03 августа 2011

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

Так что я обращаюсь к вам, ребята. У меня есть три способа передачи параметров о:

//1: By pointer
Object* foo(Object* bar) {...}

//2: By reference
Object& foo(Object& bar) {...}

//3: By value (just for completeness)
Object foo(Object bar) {...}

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

Итак: что такое «лучший» метод? Указатели? Рекомендации? Некоторая комбинация двух? Или это вообще имеет значение? Технические причины самые лучшие, но стилистические причины такие же хорошие.

Обновление: Я принял ответ YeenFei, так как он касается разницы, которая зацепила его за меня (даже если я тогда демонстративно проигнорировал его совет - мне нравится иметь NULL в качестве опции ...) , Но все высказали хорошие замечания - особенно GMan (в комментариях) и Nemo, в ответе о производительности и передаче по значению. Если вы здесь для ответов, проверьте их все!

Ответы [ 5 ]

4 голосов
/ 03 августа 2011

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

Ссылка не может быть нулевой, в то время как указатель может. Если вы имеете дело с указателем, выперед их использованием необходимо проверить, является ли данный указатель действительным (ненулевым), независимо от того, находится ли он в необработанном виде или обернут в управляемый контейнер (shared_ptr).

3 голосов
/ 03 августа 2011

Итак, я собираюсь обосновать выбор № 3.Рассмотрим следующий код:

struct Foo {
    int x;
    int y;
};

Foo
add(Foo a, Foo b)
{
    Foo result;
    result.x = a.x + b.x;
    result.y = a.y + b.y;
    return result;
}

Foo
add2(Foo &a, Foo &b)
{
    Foo result;
    result.x = a.x + b.x;
    result.y = a.y + b.y;
    return result;
}

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

Я написал main, который вызывал каждую из этих функций 10 миллиардов раз.Результат?add заняло 22 секунды, в то время как add2 заняло 26 секунд.Даже для этого тривиального примера это на 10-20% лучше производительности для версии с передачей по значению.

ОК, поэтому структура тривиальна.Но такова и функция.Чем сложнее функция, тем больше вероятность, что версия с передачей по значению будет быстрее, потому что компилятор знает, что два аргумента не «перекрываются».Это огромное преимущество для оптимизации.

Конечно, это решение должно в первую очередь основываться на семантике функции: вам нужно, чтобы значение NULL было допустимым?Если это так, очевидно, вам нужен указатель.Вам нужно изменить объекты?Затем используйте указатель или ссылку.

Но если вам не нужно изменять объекты, предпочитайте передавать их по значению, если объекты не большие и / или не имеют нетривиального конструктора копирования (например, std:: строка).Если значение на самом деле слишком медленное, передайте по ссылке-на-константе или на указатель на константу.

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

2 голосов
/ 03 августа 2011

Передача по указателю и по ссылке действительно одинакова, за исключением синтаксиса.Я предпочитаю передавать по указателю, потому что он делает вещи явными:

Object bar;
ptr_foo(&bar); // bar may change

ref_foo(bar); // can bar change? Now I need to go look at the prototype...

val_foo(bar); // bar cannot change. (Unless you use references here and there)

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

0 голосов
/ 07 февраля 2014

Использование:

  1. константная ссылка, если объект не изменен
  2. указатель, если объект изменен или может быть нулевым
  3. значение, если объект маленький, и вы заботитесь о производительности, или если вам нужна копия объекта внутри функции. Это позволяет компилятору выбрать лучший способ скопировать / переместить аргумент.
  4. std :: unique_ptr, если право собственности передается функции.
0 голосов
/ 03 августа 2011

Ссылки в любой день, если вы все сами разрабатываете.Идиоматический современный C ++ почти никогда не должен иметь нигде торчащих указателей.Динамически распределенные объекты должны перемещаться в контейнерах управления ресурсами (shared_ptr или unique_ptr, или weak_ptr, если применимо), но для большинства операций передача по (const) ссылке является основным способом передачи аргументов, которые необходимо изменить илиимеют тяжелый тип.Не забывайте, что передача по значению может быть приемлемой опцией, если у вас есть подвижные типы.

...