Есть ли функциональное различие между:
void foo(const Bar& bar) {
Bar bar_copy(bar);
// Do stuff with bar_copy
}
и
void foo(Bar bar) {
// Do stuff with bar
}
Есть ли функциональное различие между:
void foo(const Bar& bar) {
Bar bar_copy(bar);
// Do stuff with bar_copy
}
и
void foo(Bar bar) {
// Do stuff with bar
}
Да, есть ценная разница.
void foo(Bar bar)
может копировать-конструировать или перемещать-конструировать bar
, в зависимости от контекста вызова.
А когда временное значение передается в foo(Bar bar)
, ваш компилятор может сконструировать это временное непосредственно там, где ожидается bar
. Наконечник шляпы для мальчика-шаблона.
Ваша функция void foo(const Bar& bar)
всегда выполняет копирование.
Ваша функция void foo(Bar bar)
может выполнять копирование или перемещение, а возможно, ни то, ни другое.
Да, есть отличия. Хотя наиболее очевидным является изменение типа функции (и, следовательно, типа ее указателя на функцию), есть и менее очевидные последствия:
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
.
C++11 12.8/31:
При соблюдении определенных критериев реализации разрешается пропускать конструкцию копирования/перемещения объекта класса, даже если конструктор копирования/перемещения и/или деструктор объекта имеют побочные эффекты. В таких случаях реализация рассматривает источник и цель пропущенной операции копирования/перемещения просто как два разных способа ссылки на один и тот же [...]
- [...]
- когда временный объект класса, который не был привязан к ссылке (12.2), будет скопирован/перемещен в объект класса с тем же типом cv-unqualified, операция копирования/перемещения может быть опущена путем создания временного объекта непосредственно в целевом объекте. пропущенной копии/перемещения
- [...]
Очевидно, что этому критерию соответствует только версия с вызовом по значению — после передачи по ссылке параметр все-таки привязывается к ссылке. Помимо видимой разницы, это также означает, что возможность оптимизации потеряна.
Есть некоторые отличия.
void foo(const Bar& bar) {
Bar bar_copy(bar);
// Do stuff with bar_copy
}
не позволяет избежать копирования, даже если bar
был временным, а также избежать перемещения bar
.
bar
и из bar
.
- person Jarod42; 03.04.2015
Bar bar_copy(bar);
? - person gha.st   schedule 03.04.2015.cpp
. - person LogicStuff   schedule 03.04.2015Bar
, так как он помещается в стек перед вызовом функции. Вызывающий должен знать размер объекта. Поэтому он должен быть полностью указан. Ссылки и указатели передаются только как указатель (как правило), который имеет известный размер (т.е.sizeof(void*)
), поэтому вам не нужно знать, какой у вас размер объекта, пока вы не окажетесь внутри функции. - person Mark Lakata   schedule 03.04.2015