In practice, there are a lot of cases. Here are a few of them in random
order:
- void — if you don't need a return value, don't return one.
- local by value — it's the simplest, and with a little care NRVO
maximizes performance.
- local by pointer or reference — NOT!. Please don't do this.
- data member by value — excellent choice if the function is a non-static
member function, and if the data member can be copied relatively quickly,
e.g., int. If the data member is something that is slow to copy, this
has a performance penalty if you call this member function in the
inner loop of a CPU-bound application.
- data member by pointer — okay, but make sure you don't want to return it
by reference, and make sure you use Foo const* or const Foo*
if you don't want the caller to modify the data member. Since callers might
store the pointer rather than copy the data member, you should warn callers in
the member function's "contract" that they must not use the returned pointer
after the this-object dies.
- data member by reference-to-nonconst — okay, but this allows the caller
to make changes to your object's data member without your class "seeing" the
change. If you have a "set" method that changes this data member, use either
a reference-to-const or by-value instead. Another thing: since callers might
store the reference rather than copy the data member, you should warn callers
in the member function's "contract" that they must not use the returned
reference after the this-object dies.
- data member by reference-to-const — okay, but it does allow your users
to see the data type of your member variables. That means if you
ever need to change the type of your member variables, the change might break
the code that uses your class, and that's one of the main points of
encapsulation. You can ameliorate that risk by exposing a public
typedef for the type of that member variable (and therefore the type
of the reference-to-const return value), and by warning your users that they
should use the typedef rather than the raw, underlying type. Another
reality is that if the caller captures this reference, as opposed to copying
the object, then the underlying referent might change "under the caller's
nose," even though the type is
reference-to-const. Because a lot of programmers are surprised by that,
it's smart to warn callers in the member function's "contract." You should
also warn callers to discard the returned reference once the
this-object has died.
- shared_ptr to a member that was allocated via new — this has
tradeoffs that are very similar to those of returning a member by pointer or
by reference; see those bullets for the tradeoffs. The advantage is that
callers can legitimately hold onto and use the returned pointer after the
this-object dies.
- local auto_ptr or shared_ptr to freestore-allocated copy
of the datum. This is useful for polymorphic objects, since it lets you have
the effect of return-by-value yet without the "slicing" problem. The
performance needs to be evaluated on a case-by-case basis.
- others — this list is by way of example and not by way of exclusion. In
other words, this is just a starting point, not an ending point.
Murphy's Law basically guarantees that your particular needs will fall under
the last bullet, rather than any of the earlier bullets
.