6

I have read a number of stackoverflow answers about this and I am not quite satisfied from the responses so I wanted to gather them here.

When calling a function using non-primitive and complex objects for parameters it is generally advised to pass them by reference UNLESS there is a possibility of the parameter being NULL. This is mostly due to the fact that references are guaranteed to have a value so you don't have to check if a parameter reference is NULL.

When the return value of a function is a non-primitive and complex object you should generally return a pointer because it is easy to confuse a function that returns a reference with one that returns a value as they appear identical when called.

As an example take this class

struct Rectangle
{
    int x, y;
    int width, height;

    Rectangle(int x_, int y_, int width_, int height_)
        : x(x_)
        , y(y_)
        , width(width_)
        , height(height_)
    {
    }
};

class Image
{
public: 
    Image()
        : dimensions(0, 0, 0, 0)
    {}
    ~Image();

    void SetDimensions (const Rectangle& newDimensions) // Pass by reference is recommended practice
    {
        dimensions = newDimensions;
    }

    Rectangle GetDimensions()       // Returning by value is usually the best practice in this case
    {
        return dimensions;
    }
private:
    Rectangle dimensions;
};

Are these the best practices for references vs pointers in function parameters and return types? If not, please explain why not.

EDIT 1: Changed GetDimensions() to return value because it is believed efficient

user1697999
  • 215
  • 3
  • 10
  • Rectangle is not terribly complex, but treat it as a complex object for this question. – user1697999 Apr 28 '14 at 21:39
  • 4
    "When the return value of a function is a non-primitive and complex object you should generally return a pointer because...". No, usually you would return a value. But if you want reference semantics, then return a reference, unless you want to allow the possibility of a null return. – juanchopanza Apr 28 '14 at 21:39
  • And of course, you shouldn't return a reference or pointer to a locally (stack) instantiated object. – Chris Laplante Apr 28 '14 at 21:40
  • 2
    Do you mean to tell me I can't do `image.SetDimensions(Rectangle(1, 2, 3, 4));`? That seems rather strange to me. – chris Apr 28 '14 at 21:41
  • 1. Note that returning by value is efficient in C++11 thanks to move. 2. Do not use `NULL`, it can (and most likely will) have the wrong type (`int`). Use `nullptr` instead. – Baum mit Augen Apr 28 '14 at 21:41
  • 1
    @Malloc, It's still usually efficient before that with (N)RVO. – chris Apr 28 '14 at 21:42
  • 2
    SetDimensions should take a const-ref, GetDimensions should either be `const` and return a `const` ref or be non-const and return a ref (or both). I see zero need for pointers in *any* of this. It isn't like someday the member variable used for the return of `GetDimensions()` will spontaneously decide to be addressed by NULL. – WhozCraig Apr 28 '14 at 21:42
  • @Malloc What chris said. Also, move isn't always efficient. – juanchopanza Apr 28 '14 at 21:44
  • @chris: Was your question directed at me? – Chris Laplante Apr 28 '14 at 21:45
  • While some of you have done this, I would ask that you all explain why you should/should not do what you recommend... it's for the children. – user1697999 Apr 28 '14 at 21:45
  • @ChrisLaplante, No, it was about the non-const reference being taken. – chris Apr 28 '14 at 21:48
  • @chris; Ok, just checking :) – Chris Laplante Apr 28 '14 at 21:49

3 Answers3

9

A reference is a reference and a value is a value. They've different semantics and only thinking to the performance of saving a copy operation can become a serious problem.

More specifically in theory references should be used only when the receiver cares about the identity (and not just about the value) of the object. Values should be used instead if the identity is not important and the called function/method cares only about the value.

If you're absolutely sure that you're not going to run into aliasing or lifetime issues then you may decide to pass a const reference instead of a value to save the copy operation.

In this answer there's a detailed example (that, incidentally, is also talking about rectangles) of the kind of very subtle bugs that you may run into by passing references when values should have been used instead. And note also that const-correctness is not going to be of any help when dealing with aliasing and lifetime issues.

Community
  • 1
  • 1
6502
  • 112,025
  • 15
  • 165
  • 265
  • This is easily the most "correct" answer of what is a highly opinion-based question, primarily because it (the question) is so divisively situational. An excellent link to an outstanding diatribe adds even more. I would use a const-ref to `SetDimension`, but only because I can see in my head how **I** would use this class. Others will see a different picture, for different reasons, and perhaps craft a different ideology. A similar tactic on returning a const-ref for `GetDimension()` is likewise situationally debatable. An excellent response, worthy of more than my measly +1. – WhozCraig Apr 28 '14 at 22:05
  • Excellent explanation, this gives me some much-needed insight into answering the question. +1 – user1697999 Apr 28 '14 at 23:02
6

Best practice to me regarding handling object access and representation is to use this order:

  1. Use plain objects and references unless you really can't.
  2. Go for smart pointers unless you really can't.
  3. Use plain pointers, you should be really know what are you doing if you are this far.

This applies, to definition of object within code and class members, and passing them to and from functions. Basically everywhere.

And by "really can't" I mean it is impossible to write a compilable code. I do not think there are any real overheads that would make sense to change this order in current state of compilers, the standard, and their optimization capabilities.

A little explanation.

Before people had troubles with returning variables, now we got RVO and NRVO. Also c++11 gives std::function, lambdas, and move semantics. IMO all of this is an effort to be able to use objects and references with no overhead over pointers, and this is to make more maintainable, readable and better structured code.

Sometimes you need to use pointers, e.g. in case of abstract class interfaces. Smart pointers help with object management and should be a default go-to in such cases. They for example handle deletion of the objects, so you can use them safely with standard containers with out extra effort. It is also harder to "double free" them, or easier to spot mistakes.

However, I saw one guy on SO implementing their smart pointers on some game engine. They could not use blessing of standard library, and had to make their own. This is when you use plain pointers.

luk32
  • 15,812
  • 38
  • 62
  • Addendum to 3: **do it type-safe unless you really can't**. 4 would be reinterpret_cast something or binary cast something away and mess around with the code making your own rules. – Marco A. Apr 28 '14 at 21:45
  • 1
    Please explain why you should do these three. It is difficult to remember best practices if I don't know why they are best. – user1697999 Apr 28 '14 at 21:55
  • @Marco Of course it is a valid point, but I kind of hid it behind the "You should know what are you doing here." I really went other way and did not live by this order, and I think it was a bad decision. type-safety is not an issue until you get to a need of using plain pointers, and I think most people should not really have need to go so far. – luk32 Apr 28 '14 at 21:55
  • @user1697999 This is just an order in which you should handle access to your objects. I tried to give an explanation for it. Ok wait, I will explicity say it at the beginning. Hope it's better. – luk32 Apr 28 '14 at 21:57
  • To begin with, if you were working with pointers, you would be either using two pointers to the original `Rectangle` (one inside `Image`, the other outside), or would have a different `Rectangle` copy allocated in memory. The first option leads to "funny" things if you mess things up and `delete` that object twice; the second option leads to a memory leak if you forget to `delete` the rectangle when deleting the image. – SJuan76 Apr 28 '14 at 21:59
  • Add to that that with `GetDimension`, you would also create additional copies of the object (that the program would have to `delete`) or a pointer to the inner `Rectangle` object (breaking encapsulation). – SJuan76 Apr 28 '14 at 22:01
1

I would adjust SetDimensions and GetDimensions to:

// immutable access:
const Rectangle & GetDimensions() const;

// mutable access:
Rectangle & GetDimensions();

// or instead of the mutable GetDimensions():
void SetDimensions(const Rectangle & newDimensions);
glampert
  • 4,371
  • 2
  • 23
  • 49
  • There can be discussed to write `Rectangle& dim` or `Rectangle &dim`. I would prefer the second one. But `Rectange & dim` is not very clever. – Mare Infinitus Apr 28 '14 at 21:52
  • 1
    Well, yes, but this would be a matter of taste then. I personally can work with any, my only reservation is to be consistent with the style you choose throughout the whole codebase. The way I wrote, with the `&` separate by spaces on both sides is my personal style. I do the same for pointers. E.g.: `Rectangle * rect`. – glampert Apr 28 '14 at 21:58
  • This is not only a matter of taste. Most of the time, in large codebases, there is `int& var` and `int *var`. Simply because it is a reference type and a pointer variable. – Mare Infinitus Apr 28 '14 at 21:59
  • Assume I don't want mutable GetDimensions(). Why return a const reference instead of a value? Some believe returning a value is efficient enough, see the comments and responses. – user1697999 Apr 28 '14 at 22:02
  • @user1697999 Returning by values is OK, however, if you return by reference the behaviour is still the same. You can also copy the object transparently if needed. Yet you have to advantage of avoiding copy for complex objects. Also, some objects don't support copy at all, like file streams, so you have to return by ref. – glampert Apr 28 '14 at 22:08
  • @MareInfinitus I'm not going into the merit of discussing which indentation or spacing style is the "right" way. This would be like discussing where is the right place to put the `{` on if statements: same line or own line. – glampert Apr 28 '14 at 22:10