C++ return by reference and return by const-reference value is copied

Steve B. picture Steve B. · Jun 30, 2017 · Viewed 7.7k times · Source

I have some questions about returing a reference of a class member variable.

I have the following code:

#include <stdint.h>
#include <string>
#include <iostream>
#include <set>

void
PrintSet (const std::string & str, const std::set<uint32_t> & to_print)
{
  std::cout << str << " (" << to_print.size () << "): ";

  for (std::set<uint32_t>::const_iterator it = to_print.begin ();
          it != to_print.end (); ++it)
    {
      std::cout << *it << " ";
    }

  std::cout << "\n";
}

class Test
{
private:
  std::set<uint32_t> m_values;

public:

  Test () : m_values () { }

  void
  SetValues (const std::set<uint32_t> & values)
  {
    m_values = values;
  }

  const std::set<uint32_t> &
  GetValues () const
  {
    return m_values;
  }

  std::set<uint32_t> &
  GetValues ()
  {
    return m_values;
  }

  void
  Print () const
  {
    PrintSet ("TestInst", m_values);
  }
};
  1. I noticed that if I do this:

    std::set<uint32_t> returned = test.GetValues ();
    

    The variable returned gets a copy, not a reference, why?

  2. Does the const std::set<uint32_t> & GetValues () const function must contain the double const (the one on the return value and the one after the function name) ?

EDIT: Additional questions:

  1. Knowing that std::set<uint32_t> returned = test.GetValues (); creates a copy. If I do this:

    test.GetValues ().size ();
    

    In order to call the size () function (or any other member of the returned object), is a copy created temporarily or all is resolved with the returned reference?

  2. Is it bad to have three functions, return by value, by const-reference and by reference?

    // Return by value
    std::set<uint32_t>
    GetValues () const
    {
      return m_values;
    }
    
    // Return by const-reference
    const std::set<uint32_t> &
    GetValues () const
    {
      return m_values;
    }
    
    // Return by reference
    std::set<uint32_t> &
    GetValues ()
    {
      return m_values;
    }
    

Answer

Curious picture Curious · Jun 30, 2017

The variable returned gets a copy, not a reference, why?

When you use a reference to initialize a value whose type is not a reference you get a copy, for example

#include <cassert>

int main() {
    int a = 1;
    const int& a_ref = a;
    int b = a_ref;
    assert(&b != &a);
}

Does the const std::set<uint32_t> & GetValues () const function must contain the double const

The second const is applied as a member function qualifier, it means that the method can be called when the calling instance of your class is const qualified. For example

Test test;
test.GetValues(); // 1
const Test& test_ref = test;
test_ref.GetValues(); // 2

Here 1 will call the non-const version and 2 will call the method qualified with const

Further a const qualified method will not let you return non-const references to it's own values, so you must return a const reference to your member variable m_values.

So if you include the second const then you must include the first const, however if you just make the return type be a const reference then you don't need to make the method const. For example

const std::set<uint32_t>& GetValues() // 1
std::set<uint32_t>& GetValues() { return m_values; } // 2

here 1 is allowed but 2 is not allowed.

The reason this happens if you are curious, is because the implicit this pointer is const qualified to be a pointer to const in const qualified methods.


In order to call the size () function (or any other member of the returned object), is a copy created temporarily or all is resolved with the returned reference?

The size() method will be called on the reference! The best way to test things like this is to try it out in a quick test case https://wandbox.org/permlink/KGSOXDkQESc8ENPW (note that I have made the test a little more complicated than needed just for demonstration)

Is it bad to have three functions, return by value, by const-reference and by reference?

If you are exposing the user to a mutable reference, then the best way to let them make a copy (which is why you had the by-value) method is to let them make a copy themselves by initializing a non-ref qualified set with the returned reference (like you did initially)

Also in your code there is am ambiguity between the following two methods

std::set<uint32_t> GetValues() const
const std::set<uint32_t>& GetValues () const

Because all that is different is the return type, and you cannot overload a function off the return type.