Должен ли я присвоить ссылку или копию функции, возвращающей значение? - PullRequest
0 голосов
/ 30 апреля 2018

У нас есть несколько функций, возвращающих значение:

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 ]

0 голосов
/ 30 апреля 2018

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

0 голосов
/ 30 апреля 2018

ИМХО - Вы должны вернуться по ссылке, если хотите, чтобы возвращаемая вещь ссылалась на существующий объект. Вы должны возвратить по значению, когда хотите, чтобы вызывающая сторона получила копию, не связанную с каким-либо другим объектом.

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

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

0 голосов
/ 30 апреля 2018

Ваш коллега пытается выполнить работу компилятора вместо того, чтобы доверять ей, и в результате потенциально пессимистичен. 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

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

0 голосов
/ 30 апреля 2018

Это мое предложение:

Если вам нужен доступ только для чтения к объектам, возвращаемым функциями, используйте

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();
...