6

I was coding up a C++ class today, and I wrote a function that took an argument as a reference rather than a pointer, something I rarely ever do. I've always passed by pointers. So I was about to change it back, and then I realized - I have no idea if I should, or if it even matters.

So I turn to you guys. I have three ways of passing parameters about:

//1: By pointer
Object* foo(Object* bar) {…}

//2: By reference
Object& foo(Object& bar) {…}

//3: By value (just for completeness)
Object foo(Object bar) {…}

Assuming #3's out for performance reasons (yes, I know compilers have gotten pretty good at this, but still), the other two are more or less equivalent.

So: What's the "best" method? Pointers? References? Some combination of the two? Or does it even matter? Technical reasons are the best, but stylistic reasons are just as good.

Update: I've accepted YeenFei's answer, since it deals with the difference that clinched it for me (even if I then pointedly ignored his advice - I like having NULL as an option...). But everyone made good points - especially GMan (in the comments), and Nemo, in the answer dealing with performance and passing by value. If you're here for answers, check them all!

Enlico
  • 23,259
  • 6
  • 48
  • 102
Xavier Holt
  • 14,471
  • 4
  • 43
  • 56
  • 4
    By your omission of the function body, I guess you assume it's irrelevant. I assure you it's not. – Benjamin Lindley Aug 03 '11 at 02:14
  • 9
    Best *for what*? If you want to refer to an object, use a reference. If you want to point at an object, or possibly no object at all (null), use a pointer. If you want your own copy, use a value. – GManNickG Aug 03 '11 at 02:16
  • 1
    possible duplicate of [Pointer vs. Reference](http://stackoverflow.com/questions/114180/pointer-vs-reference) – Greg Hewgill Aug 03 '11 at 02:33
  • Whatever reasons there are, performance isn't one of them. – Mike Dunlavey Aug 03 '11 at 02:58
  • @Mike, there would be long debate if performance is considered :P ie the pointer dereference cost and cache/paging efficiency (due to non-localized memory)... – YeenFei Aug 03 '11 at 03:10
  • 2
    Disagree about performance not being an issue. Actually, for small objects, #3 is almost certainly going to be _fastest_... Aliasing concerns tend to cause both pointers and references to generate slower code than values. Often much slower. – Nemo Aug 03 '11 at 04:49
  • @YeenFei: It would be like a bunch of people who drive broken-down 40-year-old cars over dirt roads discussing the finer points of formula 1 auto racing. They are certainly free to do so. – Mike Dunlavey Aug 03 '11 at 11:56

6 Answers6

4

I would suggest to pass your argument by reference if it is expected to be valid. This would be a by-design optimization and save you from defensive programming.

Reference cannot be null while pointer can. If you are dealing with pointer, you will need to verify whether given pointer is valid (non-null) regardless it is in raw form or wrapped in managed container (shared_ptr), before using them.

YeenFei
  • 3,180
  • 18
  • 26
  • Actually a reference can be null (i.e. bad) if you don't take steps to prevent it. But your point about not having to check references for NULL is valid. See http://stackoverflow.com/questions/57483/what-are-the-differences-between-pointer-variable-and-reference-variable-in-c/57656#57656 – Mark Ransom Aug 03 '11 at 02:49
  • Yes but you can spot them easily when Access Violation 0x00000000 (POD) or 0x00000004 (w/ vtbl) get triggered from the function :) – YeenFei Aug 03 '11 at 03:03
  • 2
    @Mark: We've gone over this before. A reference cannot be null in a valid program, a pointer can. There is no "actually". References cannot "be" null, either, because you cannot get the address of null. – GManNickG Aug 03 '11 at 04:01
  • 1
    In C++, we are encouraged to used reference. "Use reference whenever you can, and use pointer whenever you have to" – Cheok Yan Cheng Aug 03 '11 at 05:41
  • @GMan, have you seen that link lately? I've edited it to make absolutely clear that this condition results from a bad program. I have run into this condition in production code, and I will continue to make the point at every opportunity to save someone the pain I went through. P.S. I bring it up here because it's a natural consequence of the philosophy of using references everywhere. – Mark Ransom Aug 03 '11 at 13:34
  • @Mark: I absolutely disagree. *Your bad program is a result of deferencing*, not using references. References cannot possibly be wrong, all they do is refer to **existing values**. If you have to *dereference null*, this has *nothing to do with 'forming a reference'*, you just dereferenced null. The two are totally seperate. This is like saying you can have a bad integer by dividing by zero. Integers cannot be bad, it's dividing by zero that's bad, which has nothing to do with the integer data type, just arithmetic. – GManNickG Aug 03 '11 at 19:52
  • @GMan: Point is, using references does not prevent wild/dangling handles (using that term to include pointers and references collectively). The pointer version of the program is equally as bad as the reference version, if you pass a NULL pointer to a function which specifies that the argument must point to valid data, it's no different from dereferencing a NULL pointer in your own code and passing the resulting reference to a function. Both violate the contract, both are undetectable at compile-time, both most frequently appear as an access violation inside the function. – Ben Voigt Sep 27 '11 at 02:10
  • @Ben, IMO, if we can use reference without pointer appear in any part of the parameter passing (or return), reference is a good tool. – YeenFei Oct 10 '11 at 06:12
4

So I am going to make the case for choice #3. Consider the following code:

struct Foo {
    int x;
    int y;
};

Foo
add(Foo a, Foo b)
{
    Foo result;
    result.x = a.x + b.x;
    result.y = a.y + b.y;
    return result;
}

Foo
add2(Foo &a, Foo &b)
{
    Foo result;
    result.x = a.x + b.x;
    result.y = a.y + b.y;
    return result;
}

Try examining the generated assembly. Notice how add is almost entirely register operations, nicely scheduled. Notice how add2 is lots of memory accesses without any reordering.

I wrote a main that called each of these functions 10 billion times. Result? add took 22 seconds, while add2 took 26 seconds. Even for this trivial example, that's 10-20% better performance for the pass-by-value version.

OK, so the structure is trivial. But so is the function. The more complex the function, the more likely the pass-by-value version is to be faster, because the compiler knows that the two arguments do not "overlap". This is a huge benefit to optimization.

Of course, this decision should primarily be based on the semantics of the function: Do you need NULL to be a legal value? If so, obviously you need a pointer. Do you need to modify the objects? Then use a pointer or a reference.

But if you do not need to modify the objects, prefer to pass them by value unless the objects are large and/or have a non-trivial copy constructor (e.g. std::string). If by-value really is too slow, pass by reference-of-const or pointer-to-const.

But do not underestimate the potential speed advantages of passing by value, which derive from the advantages of registers vs. memory and instruction reordering. And note that these advantages become more pronounced with every generation of CPU.

Nemo
  • 70,042
  • 10
  • 116
  • 153
  • 1
    have you tried examine the assembly difference under release mode as well ? – YeenFei Aug 03 '11 at 06:36
  • (C++ doesn't require elaborated type specifiers, by the way.) – GManNickG Aug 03 '11 at 22:10
  • @GMan: Good point. Bad habit of mine... Whenever I decide to use "struct" I feel like I am in "C mode". Fixing. – Nemo Aug 03 '11 at 23:40
  • @YeenFei: This was with "release mode"... My point is that "values" allow optimizations that pointers/references can forbid. – Nemo Aug 03 '11 at 23:42
  • 1
    Do you get any changes if you make those `const Foo&` instead of `Foo&`? – GManNickG Aug 03 '11 at 23:43
  • Here's a detailed analysis of passing by value of different objects on different architectures: http://www.macieira.org/blog/2012/02/the-value-of-passing-by-value/ – fberger Feb 07 '14 at 14:35
2

Passing by pointer and by reference are really the same, except in syntax. I prefer passing by pointer, because it makes things explicit:

Object bar;
ptr_foo(&bar); // bar may change

ref_foo(bar); // can bar change? Now I need to go look at the prototype...

val_foo(bar); // bar cannot change. (Unless you use references here and there)

The only technical preference between passing values and pointers, as you have touched on is if the class is large enough to make its passing slow.

Dave
  • 10,964
  • 3
  • 32
  • 54
  • well, copy-constructing a given object is always slower than pointer/reference. – YeenFei Aug 03 '11 at 02:36
  • @YeenFei, passing by value is faster if the object doesn't contain much data (such as a single int). – Mark Ransom Aug 03 '11 at 02:43
  • @Mark Ransom, that is an interesting point (although we all knew objects designed are much larger than that most of the time). Do you mind to point me to the article explaining the reasons ? – YeenFei Aug 03 '11 at 02:59
  • Except every function that accepts a pointer has to handle null, if your program is to be correct. – GManNickG Aug 03 '11 at 04:00
  • 1
    @YeenFei: You are completely wrong. Although the argument passing itself is likely faster, the generated code is almost certainly worse and will more than make up for it. Excessive memory loads/stores because of aliasing concerns are the Achilles' heel of C++ optimization. – Nemo Aug 03 '11 at 04:51
  • @Nemo, we know that dereferencing pointer (or using reference) will require additional code, but aren't instantiating (and cleanup in the end) additional object from pass-by-value having the same drawback too ? – YeenFei Aug 03 '11 at 05:36
  • @YeenFei: If the objects are small structs of POD data, there is no cleanup. OK so "completely wrong" was an overstatement on my part :-). But so was your claim of by-value being "always slower than pointer/reference". For a small object like a "point" or "rectangle", by-value actually can result in faster code, depending on your function, compiler, and CPU – Nemo Aug 03 '11 at 05:40
  • @Dave - Passing pointers are only explicit at the first level. When you pass it on to another function you still cannot see if it is passed as a pointer or not. – Bo Persson Aug 03 '11 at 07:14
  • @Bo Persson - I have to disagree; although the semantics change, the expressivity does not. Given a pointer, the functions would be called as `ptr_foo(bar)` and `val_foo(*bar)`. It remains obvious which can change the object, and it's much easier to know the type of a variable just declared than the prototype for a function. – Dave Aug 03 '11 at 18:09
  • @Dave, you can have pointer to const, or even const pointer to const(and you need to look at prototype just like reference usage...) – YeenFei Aug 04 '11 at 09:20
0

Use:

  1. const reference if the object is not modified
  2. pointer if the object is modified or can be null
  3. value if the object is small and you care about performance or if you need a copy of the object inside the function. This allows the compiler to pick the best way to copy/move the argument.
  4. std::unique_ptr if ownership is transferred to the function.
fberger
  • 681
  • 5
  • 4
0

You can take a look at https://www.boost.org/doc/libs/1_51_0/libs/utility/call_traits.htm library, it converts the type to the best arguments type automatically.

Konstantin Nikitin
  • 2,256
  • 1
  • 17
  • 12
0

References any day, if you're designing everything yourself. Idiomatic modern C++ should almost never have raw pointers sticking out anywhere. Dynamically allocated objects should travel in resource managing containers (shared_ptr or unique_ptr, or weak_ptr if applicable), but for most operations passing by (const) reference is the primary way to pass arguments that need to be modified or that are of a heavy-weight type. Don't forget that passing by value may be a viable option if you have movable types.

Kerrek SB
  • 464,522
  • 92
  • 875
  • 1,084