Should I Return a const reference

Started by
13 comments, last by visitor 15 years, 10 months ago
I'm trying to optimize some of my code, and I was wondering if its worth returning a const reference over a copy of the object I am returning? Should I be worried that people can try to cast it to a non-const object and meddling with the data? Example:

#include "MyOtherClass.h"

class MyClass
{
  MyOtherClass foo;
public:
  // ... code ...
  const SomeOtherClass &myFunciton()
  {
    return foo;
  }
}

Also, Returning references to local values is our of the question... Getters/Setters only apply? EDIT: Spelling nazis attacked me.
------------Anything prior to 9am should be illegal.
Advertisement
Quote:Original post by RealMarkP
I'm trying to optimize some of my code, and I was wondering if its worth returning a const reference over a copy of the object I am returning?


Yes, if copying that object is the bottleneck you found by profiling the code.

Quote:Original post by RealMarkP
Should I be worried that people can try to cast it to a non-const object and meddling with the data?


Yes, but it's their problem.


Quote:Original post by RealMarkP
Also, Returning references to local values is our of the question...


Correct

Quote:Original post by RealMarkP
Getters/Setters only apply?


What?
I'm in the same boat as to the getters / settings.

<source lang="C++">
void Class::setFont(Font* font) {
this->m_font = font;
}

Font* Class::getFont() const {
return this->m_font;
}
</source>

OR

<source lang="C++">
void Class::setFont(const Font& font) {
this->m_font = &font
}

Font& Class::getFont() const {
return *this->m_font;
}
</source>
Author Freeworld3Dhttp://www.freeworld3d.org
Quote:Original post by RDragon1
Quote:Original post by RealMarkP
I'm trying to optimize some of my code, and I was wondering if its worth returning a const reference over a copy of the object I am returning?


Yes, if copying that object is the bottleneck you found by profiling the code.



Good point. Ill probably leave this to a last-resort scenario where my copy constructor can't be optimized further.
------------Anything prior to 9am should be illegal.
No. You shouldn't do this for a number of reasons.

First of all, you get no optimization in the typical usage case:


const Foo& getValue(){...}
Foo f = getValue();


In the above snippet, the function returns a const reference but the l-value is declared by value. This means the copy constructor is invoked and hereafter you're dealing with a copy of the member anyways.

There are only two good reasons to return reference types from functions:

(1) So functions can be l-values. Ex:

Foo& Foo::operator=(const Foo& rhs){...};
...
Foo a, b, c;
a = b = c; // Expands to (a=(b.operator=(c)));


(2) So we can use value semantics on functions

const Bar& Foo::operator[](std::size_t idx){...};
...
Foo a;
...
a[24].doStuff(); // Call Bar::doStuff() const


If you are not doing either (1) or (2), do not return a const or non-const reference from a function.

Returning references to members breaks encapsulation, which is entirely contrary to OOP. An object's encapsulation is proportional to how many functions can access its internals. Every time you hand out a handle to an object's internals you are breaking encapsulation and increasing coupling.

I won't even get into the headaches this causes in a multi-threaded environment.

Most importantly you shouldn't be afraid of returning non-trivial objects by value. Any extra cost incurred by return-by-value is trivially optimized through NRVO/RVO Optimization and every respectable C++ compiler does it. So if you need to return a copy, just return a copy.

[edit]

Fixed associativity error when expanding operator=()

[Edited by - fpsgamer on June 16, 2008 10:17:34 AM]
Gotcha, thats what I needed to know.

Thanks.
------------Anything prior to 9am should be illegal.
Quote:Original post by fpsgamer
Returning references to members breaks encapsulation, which is entirely contrary to OOP.


Please explain how returning a constant reference breaks encapsulation. In particular, please provide an example of how you could use the returned object in a way that is both legal (that is, it doesn't make any assumptions beyond the fact that it's a constant reference) and would prevent me from later changing the return value to a non-reference object instead.
I don't know about breaking encapsulation but it does restrict your implementation a little. It prevents the possibility of having this function derive the return value from other member variables to return the result because you're forced to return a reference to something that exists for at least the lifetime of the class instance, i.e. probably a member variable.

e.g.
class foo() {    int x, y;    int getX() { return x; }    int getY() { return y; }};class bar() {    int x, y;    const int &getX() { return x; }    const int &getY() { return y; }};
No problems so far, but now we decide to skew the coordinate system returned, like this:
class foo() {    int x, y;    int getX() { return x + y; }    int getY() { return x - y; }};class bar() {    int x, y;    const int &getX() { return x + y; } // uh oh can't do this!    const int &getY() { return x - y; } // uh oh can't do this!};
'foo' is fine, but 'bar' wont compile and you're forced to change the class interface.
This may be what fpsgamer was getting at.
"In order to understand recursion, you must first understand recursion."
My website dedicated to sorting algorithms
Quote:Original post by fpsgamer


There are only two good reasons to return reference types from functions:

(1) So functions can be l-values. Ex:

Foo& Foo::operator=(const Foo& rhs){...};
...
Foo a, b, c;
a = b = c; // Expands to (a.operator=(b)) = c;



I would agree that this is a useful use but the example is not quite right. If that statement expanded this way then a would first be set equal to b and then to c, leaving b unmodified.
In fact it seems that you could just as well return by value and operator chaining would still work. So a reference is returned for performance only (as OP asked)?

Quote:
(2) So we can use value semantics on functions

const Bar& Foo::operator[](std::size_t idx){...};
...
Foo a;
...
a[24].doStuff(); // Call Bar::doStuff() const


Again, this is something that would work as well if a copy was returned (since a const function is called, it doesn't even matter much if it is a copy or not). So again you return a reference only for performance? (If this was the OP's main usage case, would you say he's doing something bad?)

I agree that returning references to members limits your implementation choices. For example, the wxWidget's library returns large things by pointer (to allow returning NULL), and it also wants to keep the returned objects polymorphic.

I guess returning references/pointers to large objects can be a valid optimization, but you should be aware of the pros and cons and consider that it won't pay off for small objects.

Quote:Original post by ToohrVyk
Please explain how returning a constant reference breaks encapsulation.

Store it as a const reference, destroy the object that you got it from, access the reference. The internal lifetime of that object is now very much on display. But this is a minor nitpick... he who stores a returned reference without thinking about lifetime management has forgotten what language he's programming in. I agree: Encapsulation is not a good reason to avoid returning const references.

This topic is closed to new replies.

Advertisement