7

I want to pass an object by smart-pointer reference to a function. The function may change the value of the referenced object, but may not change the reference itself. There are two obvious ways to handle this.

The first approach to pass the shared_ptr by value - it is the reference, so doesn't itself need to be passed by reference. The apparent issue with this is copying of the reference, which suggests some reference-counting overhead.

void foo (shared_ptr<bar> p)

The second approach is to pass the shared_ptr by const reference - avoiding the copying of the shared_ptr instance, but instead implying that accesses to the referenced object need two layers of dereferencing instead of one.

void foo (const shared_ptr<bar> &p)

In practice, these theoretical overheads will normally be trivial and irrelevant. Which suggests to me that instead of choosing one approach or the other for each individual case, I should almost always follow some standard convention. Which leads to the question...

Is there a standard convention for which of these approaches I should normally choose? If so, which is the conventional choice?

EDIT - Probably worth mentioning - one reason to consider the pass-by-const-reference case is because there's a pre-existing convention that most class/struct instances are passed by const-reference rather than by value, and shared_ptr is a class. Of course it isn't a heavyweight class (the cost of copying is small), so the reasons behind that older convention may not apply.

  • I missed a possible duplicate before posting - http://stackoverflow.com/questions/5330793/shared-ptr-pass-by-value-vs-pass-by-reference - but this may be more specific to a particular requirement rather than about a general convention. –  Dec 04 '11 at 21:13
  • 1
    ¤ There are possible micro-inefficiencies anyway, so go for *clarity*. Pass by value. That's what you would do for a raw pointer, i.e. it is the established convention. Anything else makes a reader wonder why. Which would be miscommunication, unclear. Cheers & hth., – Cheers and hth. - Alf Dec 04 '11 at 21:24
  • @Alf - I've been thinking about what you said. One response is my edit, but that doesn't mean I disagree. A raw reference cannot be passed by reference, too (though a raw pointer can), so I'm not sure "convention" is the right word, but a compulsory practice is certainly conventional. I think you have a point, but I still have doubts - in C++, where passing struct/class instances by const-reference is so commonly done (for efficiency reasons, but also as a convention), does doing so imply anything about intent? Maybe it can, but probably not in any clear/definitive way. –  Dec 04 '11 at 22:17
  • @AlfP.Steinbach "_There are possible micro-inefficiencies anyway_" have you measured these? – curiousguy Dec 07 '11 at 03:35
  • @curiousguy: it's impossible for me to measure the OP's code. one has a possible fixed set-up overhead, the other way has a possible overhead per access. the behavior thus depends both on which of these possibilities (if any) manifest themselves, and how much accessing is going on for each function call. cheers & hth., – Cheers and hth. - Alf Dec 07 '11 at 03:44

4 Answers4

13

Always pass shared_ptrs by value. If you pass a reference, you might run into the problem that a call to a function of the object managed by the shared_ptr might just reset it, and now you've got a dangling pointer. If you pass by value, you ensure that the object will survive the current function call.

See here for much, much more information.


Example:

#include <memory>
#include <iostream>

std::shared_ptr<int> ptr(new int(42));

void foo(){
  ptr.reset();
}

void bar(std::shared_ptr<int> const& p){
  foo();
  std::cout << *p;
}

int main(){
  bar(ptr);
}

This should be taken with a pinch of salt. It can be adapted to prove that no type should ever be passed by const-reference - see for example http://ideone.com/1IYyC, which Benjamin Lindley pointed out in the comments.

However, more complex variations of this kind of issue do arise by accident in practice. This is the reason, for example, why we are warned that iterators (as well as const-reference return values) are invalidated by methods that mutate the referenced container. These rules are easy enough to follow in general, but occasionally more indirect and unexpected examples can catch people by surprise.

That being the case, it's best to avoid the extra layer of referencing when it's not needed.

Community
  • 1
  • 1
Xeo
  • 129,499
  • 52
  • 291
  • 397
  • How could a call to a function of the object managed by the pointer reset the pointer, or have any effect on the pointer? Or am I not reading that right? – Benjamin Lindley Dec 04 '11 at 21:18
  • Perhaps an example? How and what does the object reset? – UncleBens Dec 04 '11 at 21:19
  • @Benjamin: An object that is always managed by a `shared_ptr` (inheriting from `enable_shared_from_this` and stuff) could call back to the parent that manages it and that could reset it.. or something like that. It wasn't too long ago that I read the text where this exact reason with a good explanation was given, if only I could find it... – Xeo Dec 04 '11 at 21:20
  • @Benjamin Lindley mmm vector reallocation, string reallocation , invalidating references, ect... – johnathan Dec 04 '11 at 21:21
  • @Benjamin, UncleBens: Found it! :D – Xeo Dec 04 '11 at 21:22
  • Good question. In the pass-by-reference case the shared_ptr is being passed by `const` reference, which I would expect to ensure at least logical constness if not complete immutability. If this is a real issue, I'd like to see an example and a detailed explanation. –  Dec 04 '11 at 21:24
  • 1
    @Steve: Just because the pointer can't be changed *through this reference* doesn't mean it can't be changed from the outside. – Xeo Dec 04 '11 at 21:28
  • 1
    @xeo: i agree with the always-pass-by-value. but when the formal argument is reference to const, then no harm can come of it (that is, other than the usual of possible aliasing for a ref-to-const argument, which I have *never* encountered, and which applies equally for any use of ref-to-const formal argument). only a possible micro-inefficiency. cheers, – Cheers and hth. - Alf Dec 04 '11 at 21:30
  • 2
    @Xeo - agreed, but that's a standard pass-by-const-reference issue that doesn't normally worry anyone. If multi-threading is an issue, this is the least of your problems. If you save a copy of that reference, assuming that means copying the shared_ptr instance, you should be OK. Otherwise, the issue can't arise - nothing outside the function gets to change the reference while you're using it because nothing outside the function gets to execute until the function exits and the parameter dies anyway. –  Dec 04 '11 at 21:40
  • @Steve: Well, you could call functions which kill the `shared_ptr` from inside your function. – Xeo Dec 05 '11 at 10:08
  • @Xeo - no, because it's a **const** reference to a `shared_ptr`. OK, technically there are ways to cast away that constness, but if people are doing that just for fun then the source of your problem is **not** the pass-by-const-reference. People could e.g. randomly corrupt memory for fun too, changing the values of `shared_ptr` instances (and other things) that were copied. None of this means that passing by const-reference is the right convention - at this point I'm leaning strongly towards pass-by-value - but I have doubts about the rationale. –  Dec 05 '11 at 22:52
  • @Steve: You do not seem to understand what I mean. I'll edit in a contrived example, so bear with me for a second. Edit: There, done. – Xeo Dec 05 '11 at 22:59
  • @Xeo: Examples involving global variables aren't very relevant, as if you use a global variable, then you're begging for such problems- this is nothing specific to `shared_ptr`. – Puppy Dec 05 '11 at 23:03
  • @DeadMG: I said it was a contrived example. Now just put the `shared_ptr` and `foo` in one class, and `bar` in another... – Xeo Dec 05 '11 at 23:06
  • 1
    @Xeo: Here's a contrived example that shows you should not pass bools by reference. http://ideone.com/1IYyC – Benjamin Lindley Dec 05 '11 at 23:11
  • @Xeo - good point - I'd agree with DeadMG except it is just about possible for it to happen by accident. I know that because I immediately recognised it as a simplified variant of something I've done - not with `shared_ptr` (which I've obviously rarely used so far in practice), but for other types. You have a container. You search the container for a "value", which you const-reference rather than copy. You call another operation which mutates the same container - mutating or moving the referenced value. Whoops! Rare, and explicitly warned about e.g. for STL containers, but you get the accept. –  Dec 05 '11 at 23:40
  • So, you are opposed to pass-by-const-reference in every cases? What about non-const references? What about pointers? What about `this`? – curiousguy Dec 07 '11 at 03:34
  • @curiousguy: For `shared_ptr`? Yes. In general? No. Did you even read the link I provided? It's the first sentence why `shared_ptr` should be passed by value. It doesn't apply to other things, since they don't have the same sematics as `shared_ptr`. – Xeo Dec 07 '11 at 03:41
  • So, you are opposed to pass-by-const-reference for `vector`? What about non-const references? What about pointers? – curiousguy Dec 07 '11 at 04:20
0

If the function manipulating the object it should not be relevant if the object is contained in a smart pointer. It should accept a T&. Free functions that manipulate values are considered bad style by some. Taking the object by reference to const and returning a new value can be cleaner.

pmr
  • 58,701
  • 10
  • 113
  • 156
0

I've been passing by const& based on the idea that somewhere up the call stack someone should be holding a copy of the shared_ptr and therefore its life is guaranteed for my entire execution.

In the case that you are executing on a different thread, you need a copy - and for that matter anyone that stores the shared_ptr (not just uses it for the duration of their method) needs to store a copy, not a reference.

And, now that I've written that I'll go and read why people seem to be supporting the opposite mentality.

David
  • 27,652
  • 18
  • 89
  • 138
-1

An example to what @Xeo said:

void foo(shared_ptr<bar> &p)
{
    p.reset(); // ref_count == 0; memory is freed.
}

shared_ptr<bar> p(new bar); // ref_count == 1
foo(p);

This does not happen for const shared_ptr<bar> &p.

Mateen Ulhaq
  • 24,552
  • 19
  • 101
  • 135
  • I think you're missing a `p.reset()` or something like that? – Xeo Dec 04 '11 at 21:29
  • 1
    `shared_ptr` is `const` inside the function, you can't call `reset` on it. – Cat Plus Plus Dec 04 '11 at 21:38
  • For clarity - what Cat Plus Plus said is true of the examples in my original question - the original alternatives didn't include `shared_ptr &p`, they were `shared_ptr p` and (the relevant one) `const shared_ptr &p`. –  Dec 04 '11 at 22:09
  • This is getting even more ridiculous. Why else would you pass a non-const reference if you don't intend to modify the argument? – UncleBens Dec 04 '11 at 22:11
  • @UncleBens - the original question didn't suggest passing a non-const reference at all. If you're saying that this answer is doing something that's perfectly reasonable, I agree - there is no ban on functions having side-effects in C or in C++, and passing by non-const reference communicates the intent to modify the referenced object (in this case the `shared_ptr` instance), so this "side-effect" is a perfectly reasonable intentional effect. –  Dec 05 '11 at 00:37
  • What @Xeo has in mind is something similar to the self-assignment problem. If an overloaded assignment operator is implemented as `delete* m_ptr; m_ptr = new Object(rhv.m_ptr);`, it will blow up when `this == &rhv`. - There needs to be another way to access a non-const reference to the same shared_ptr. – UncleBens Dec 05 '11 at 07:27