4

We have a bunch of value returning functions:

Foo function_1(){
    Foo f;
    // ...
    return f;
}

Bar function_2(){
    Bar b;
    // ...
    return b;
}

Baz function_3(){
    Baz b;
    // ...
    return b;
}

I'm using them to create instantiations of local variables:

void example(){  
    //...
    const auto foo = function_1();
    //...
    const auto bar = function_2();
    //...
    const auto baz = function_3();
}

However, my teammates keep asking me to convert all my instantiations to use &:

void example(){  
    //...
    const auto& foo = function_1();
    //...
    const auto& bar = function_2();
    //...
    const auto& baz = function_3();
}

The rationale for doing this seems to match the following question:
Why not always assign return values to const reference?

I understand that auto& x = and auto x = are asking for two different things, and the behavior might be different based on the implementation of the function.

That being said, I'm looking for clarification about the differences (if any) when choosing to do this on value returning functions? Is the behavior guaranteed to be the same?

If its a value returning function how do I decide between const auto& vs const auto? (presumably the const doesn't matter here?). I couldn't find any advice about this in the C++ Core Guidelines. I'm hoping its not an opinioned question.

Trevor Hickey
  • 36,288
  • 32
  • 162
  • 271
  • 2
    I say the battle is between `auto&&` and `const auto&&`. But then it's just constness so... – DeiDei Apr 30 '18 at 19:24

4 Answers4

9

Your colleague is trying to do the compiler's job instead of trusting it, and is potentially pessimizing as a result. NRVO is very well supported, and if the functions are written with value semantics, NRVO can elide multiple copies. Binding to a reference will prevent that, since a reference variable will not satisfy the conditions for this optimization. A simple test to demonstrate:

#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;
}

Which gives the output:

Created
===========
Created
Copied

One object total when utilizing value semantics, but a redundant copy when using references. Depending on how expansive the copy (or even move) is, you could see a noticeable performance difference.

StoryTeller - Unslander Monica
  • 165,132
  • 21
  • 377
  • 458
2

This is my suggestion:

If you need read-only access to the objects returned by the functions, use

const auto& foo = function_1();

If you want to be able to modify the returned objects or don't want to take the chance that the object might be deleted by some other function without your knowledge, make a copy.

auto foo = function_1();

Additional info in response to comments

When the following program is compiled with 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();
}

I get the following output (no output from the constructor).

-------------------------
-------------------------

When the same program is compiled with g++ -std=c++11 -Wall -fno-elide-constructors

I get the following output.

-------------------------
In copy constructor
In copy constructor
-------------------------
In copy constructor

If a compiler does not support copy elision by default, an additional copy is made when using

auto f1 = function_1();
Community
  • 1
  • 1
R Sahu
  • 204,454
  • 14
  • 159
  • 270
2

IMHO - You should return by reference when you want the thing you return to refer to an existing object. You should return by value when you want the caller to get a copy that's unrelated to some other object.

For example; if you have a function returning an object from a container and you want the caller to be able to update the original value, then return a reference. If the caller should just get their own copy to work with that should not affect the original object in the container, then return by value.

My rule of thumb is that value semantics are the easiest to work with - both when returning objects and taking them as arguments, so I prefer taking/returning by value unless I have an explicit reason not to.

Jesper Juhl
  • 30,449
  • 3
  • 47
  • 70
-1

The benefit of reference is that it is always functional. If captured by value, copy elision may make it as if it is captured by reference; but if copy constructor is deleted, capture by value triggers a move followed by destruction of the temporary which is a bit less efficient; and if the move constructor too is deleted, compilation fails. Now, the choice must be made between mutable and const: If mutable, then you'll need mutable rvalue reference; otherwise, const lvalue reference.

Red.Wave
  • 2,790
  • 11
  • 17