Является ли передача по ссылке, а затем копирование и передача по значению функционально разными?

Есть ли функциональное различие между:

void foo(const Bar& bar) {
  Bar bar_copy(bar);
  // Do stuff with bar_copy
}

и

void foo(Bar bar) {
  // Do stuff with bar
}

person wrhall    schedule 02.04.2015    source источник
comment
Я полагаю, вы имеете в виду Bar bar_copy(bar);?   -  person gha.st    schedule 03.04.2015
comment
упс, да. Я написал это правильно в моем термине и не копировал это. Извини.   -  person wrhall    schedule 03.04.2015
comment
Я использовал это для объявления функций, принимающих неполные типы (по ссылке), определения которых позже включаются в файл .cpp.   -  person LogicStuff    schedule 03.04.2015
comment
@LogicStuff, ты используешь верхний? Почему нельзя сделать это с нижним?   -  person wrhall    schedule 03.04.2015
comment
@wrhall Потому что параметр в объявлении функции должен иметь тип complete, если он не передается по ссылке или указателю.   -  person LogicStuff    schedule 03.04.2015
comment
@wrhall - компилятор должен знать, как создать (и, конечно же, выделить) Bar, так как он помещается в стек перед вызовом функции. Вызывающий должен знать размер объекта. Поэтому он должен быть полностью указан. Ссылки и указатели передаются только как указатель (как правило), который имеет известный размер (т.е. sizeof(void*)), поэтому вам не нужно знать, какой у вас размер объекта, пока вы не окажетесь внутри функции.   -  person Mark Lakata    schedule 03.04.2015
comment
@LogicStuff: Неправильно. Он должен быть завершен только в момент использования и в момент определения. Декларация должна знать только имя.   -  person Xeo    schedule 03.04.2015


Ответы (3)


Да, есть ценная разница.

void foo(Bar bar) может копировать-конструировать или перемещать-конструировать bar, в зависимости от контекста вызова.

А когда временное значение передается в foo(Bar bar), ваш компилятор может сконструировать это временное непосредственно там, где ожидается bar. Наконечник шляпы для мальчика-шаблона.

Ваша функция void foo(const Bar& bar) всегда выполняет копирование.

Ваша функция void foo(Bar bar) может выполнять копирование или перемещение, а возможно, ни то, ни другое.

person Drew Dormann    schedule 02.04.2015
comment
Вы также упускаете возможность оптимизации, поскольку, когда инициализатор параметра является временным, копирование или перемещение можно полностью исключить. - person template boy; 03.04.2015

Да, есть отличия. Хотя наиболее очевидным является изменение типа функции (и, следовательно, типа ее указателя на функцию), есть и менее очевидные последствия:

Перемещаемый, но не копируемый Bar

Например, предположим следующий вызов foo:

foo(Bar());

Для первой версии это будет передано ссылкой на const bar, а затем скопировано с помощью конструктора копирования. Для второй версии компилятор сначала попытается использовать конструктор перемещения.

Это означает, что только вторая версия может быть вызвана только типами, которые можно построить только с помощью перемещения, например std::unique_ptr. На самом деле принудительное копирование вручную даже не позволит скомпилировать функцию.

Очевидно, это можно смягчить, добавив небольшое усложнение:

void foo(Bar&& bar) {
    // Do something with bar.
    // As it is an rvalue-reference, you need not copy it.
}

void foo(Bar const& bar) {
    Bar bar_copy(bar);
    foo(std::move(bar_copy));
}

Спецификаторы доступа

Интересно, что есть еще одно отличие: контекст, в котором проверяются права доступа.

Рассмотрим следующее Bar:

class Bar
{
    Bar(Bar const&) = default;
    Bar(Bar&&) = default;

public:
    Bar() = default;

    friend int main();
};

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

void fooA(const Bar& bar)
{
    //Bar bar_copy(bar); // error: 'constexpr Bar::Bar(const Bar&)' is private
}

void fooB(Bar bar) { } // OK

Поскольку мы объявили main другом, следующий вызов разрешен (обратите внимание, что друг не нужен, если , например, фактический вызов был сделан в static функции-члене Bar):

int main()
{
    fooB(Bar()); // OK: Main is friend
}

Полнота Bar на месте вызова

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

Копировать побочные эффекты Elision

C++11 12.8/31:

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

  • [...]
  • когда временный объект класса, который не был привязан к ссылке (12.2), будет скопирован/перемещен в объект класса с тем же типом cv-unqualified, операция копирования/перемещения может быть опущена путем создания временного объекта непосредственно в целевом объекте. пропущенной копии/перемещения
  • [...]

Очевидно, что этому критерию соответствует только версия с вызовом по значению — после передачи по ссылке параметр все-таки привязывается к ссылке. Помимо видимой разницы, это также означает, что возможность оптимизации потеряна.

person gha.st    schedule 02.04.2015

Есть некоторые отличия.

void foo(const Bar& bar) {
  Bar bar_copy(bar);
  // Do stuff with bar_copy
}

не позволяет избежать копирования, даже если bar был временным, а также избежать перемещения bar.

person Jarod42    schedule 02.04.2015
comment
Делает ли это строго хуже, если вы можете просто передать значение [т.е. есть комментарий к вопросу, в котором говорится, что они используют передачу по ссылке, а затем копируют функции, принимающие неполные типы, которые они, по-видимому, не могут использовать в проходе -версия по значению]? - person wrhall; 03.04.2015
comment
@wrhall: Если вы передаете по значению, вы разрешаете перемещение построения из bar и из bar. - person Jarod42; 03.04.2015
comment
Верно -- так что в случае, когда вы можете передавать по значению, вы, вероятно, должны это сделать, верно? Или есть причины не делать этого? - person wrhall; 03.04.2015
comment
Как указано в комментарии, для форвардного объявления. Для простоты предпочтительным способом является переход по ссылке const в другом случае, который встречается чаще. - person Jarod42; 03.04.2015