Должен ли я присвоить ссылку или копию функции, возвращающей значение?
У нас есть несколько функций, возвращающих значение:
Foo function_1(){
Foo f;
// ...
return f;
}
Bar function_2(){
Bar b;
// ...
return b;
}
Baz function_3(){
Baz b;
// ...
return b;
}
Я использую их для создания экземпляров локальных переменных:
void example(){
//...
const auto foo = function_1();
//...
const auto bar = function_2();
//...
const auto baz = function_3();
}
Тем не менее, мои товарищи по команде продолжают просить меня преобразовать все мои экземпляры для использования &
:
void example(){
//...
const auto& foo = function_1();
//...
const auto& bar = function_2();
//...
const auto& baz = function_3();
}
Логическое обоснование для этого, кажется, соответствует следующему вопросу:
Почему бы не всегда присваивать возвращаемые значения константной ссылке?
Я это понимаю auto& x =
а также auto x =
просят две разные вещи, и поведение может отличаться в зависимости от реализации функции.
При этом я ищу разъяснения относительно различий (если таковые имеются) при выборе сделать это в функциях возврата значений? Гарантируется ли поведение таким же?
Если это функция, возвращающая значение, как я могу выбрать между const auto&
против const auto
? (предположительно, const здесь не имеет значения?). Я не мог найти никакого совета по этому поводу в C++ Core Guidelines. Я надеюсь, что это не взвешенный вопрос.
4 ответа
Ваш коллега пытается выполнить работу компилятора вместо того, чтобы доверять ей, и в результате потенциально пессимизирует. NRVO очень хорошо поддерживается, и если функции написаны с семантикой значений, NRVO может удалить несколько копий. Связывание со ссылкой предотвратит это, поскольку ссылочная переменная не будет удовлетворять условиям этой оптимизации. Простой тест для демонстрации:
#include <iostream>
struct Test {
Test() { std::cout << "Created\n"; }
Test(const Test&) { std::cout << "Copied\n"; }
};
Test foo() {
Test t;
return t;
}
Test bar_good() {
const auto t = foo();
return t;
}
Test bar_bad() {
const auto& t = foo();
return t;
}
int main() {
const auto good = bar_good(); (void)good;
std::cout << "===========\n";
const auto& bad = bar_bad(); (void)bad;
}
Который дает вывод:
Created
===========
Created
Copied
Всего один объект при использовании семантики значения, но избыточная копия при использовании ссылок. В зависимости от того, насколько обширна копия (или даже перемещена), вы можете увидеть заметную разницу в производительности.
ИМХО - Вы должны вернуться по ссылке, когда хотите, чтобы возвращаемая вещь ссылалась на существующий объект. Вы должны возвратить по значению, когда хотите, чтобы вызывающая сторона получила копию, не связанную с каким-либо другим объектом.
Например; если у вас есть функция, возвращающая объект из контейнера, и вы хотите, чтобы вызывающая сторона могла обновить исходное значение, тогда верните ссылку. Если вызывающая сторона должна просто получить собственную копию для работы с ней, это не должно влиять на исходный объект в контейнере, а затем вернуть по значению.
Мое эмпирическое правило заключается в том, что с семантикой значений легче всего работать - как при возврате объектов, так и при их использовании в качестве аргументов, поэтому я предпочитаю принимать / возвращать по значению, если у меня нет явной причины не делать этого.
Это мое предложение:
Если вам нужен доступ только для чтения к объектам, возвращаемым функциями, используйте
const auto& foo = function_1();
Если вы хотите иметь возможность изменять возвращаемые объекты или не хотите, чтобы этот объект мог быть удален какой-либо другой функцией без вашего ведома, сделайте копию.
auto foo = function_1();
Дополнительная информация в ответ на комментарии
Когда следующая программа скомпилирована с g++ -std=c++11 -Wall
#include <iostream>
struct Foo
{
Foo() {}
Foo(Foo const& copy) { std::cout << "In copy constructor\n"; }
};
Foo function_1(){
Foo f;
return f;
}
int main()
{
std::cout << "-------------------------\n";
auto f1 = function_1();
std::cout << "-------------------------\n";
const auto& ref = function_1();
}
Я получаю следующий вывод (без вывода из конструктора).
-------------------------
-------------------------
Когда та же самая программа компилируется с g++ -std=c++11 -Wall -fno-elide-constructors
Я получаю следующий вывод.
-------------------------
In copy constructor
In copy constructor
-------------------------
In copy constructor
Если компилятор не поддерживает удаление по умолчанию, дополнительная копия создается при использовании
auto f1 = function_1();
Преимущество ссылки в том, что она всегда функциональна. Если захвачено по значению, копия elision может сделать это так, как если бы оно было захвачено по ссылке; но если конструктор копирования удален, захват по значению запускает движение, сопровождаемое уничтожением временного, что немного менее эффективно; и если конструктор перемещения тоже удален, компиляция завершится неудачно. Теперь нужно сделать выбор между mutable и const: если он изменяемый, то вам понадобится изменяемая ссылка на rvalue; в противном случае, постоянная ссылка.