306

When a function takes a shared_ptr (from boost or C++11 STL), are you passing it:

  • by const reference: void foo(const shared_ptr<T>& p)

  • or by value: void foo(shared_ptr<T> p) ?

I would prefer the first method because I suspect it would be faster. But is this really worth it or are there any additional issues?

Could you please give the reasons for your choice or if the case, why you think it does not matter.

Olivia Stork
  • 4,312
  • 4
  • 23
  • 39
Danvil
  • 20,344
  • 18
  • 61
  • 85
  • 16
    The problem is those aren't equivalent. The reference version screams "I'm going to alias some `shared_ptr`, and I can change it if I want.", while the value version says "I'm going to copy your `shared_ptr`, so while I can change it you'll never know.) A const-reference parameter is the real solution, which says "I'm going to alias some `shared_ptr`, and I promise not to change it." (Which is extremely similar to by-value semantics!) – GManNickG Jul 22 '10 at 16:01
  • 2
    Hey i would be interested in your guys opinion about *returning* a `shared_ptr` class member. Do you do it by const-refs? – Johannes Schaub - litb Jul 22 '10 at 16:01
  • Third possibility is to use std::move() with C++0x, this swaps both shared_ptr – Tomaka17 Jul 22 '10 at 16:04
  • @Johannes: I would return it by const-reference just to avoid any copying/ref-counting. Then again, I return all members by const-reference unless they're primitive. – GManNickG Jul 22 '10 at 16:31
  • possible duplicate of [C++ - pointer passing question](http://stackoverflow.com/questions/3246712/c-pointer-passing-question) – kennytm Jul 22 '10 at 17:02
  • @Johannes: There, as a comment to jalf's answer, was [my comment](http://stackoverflow.com/questions/3246712/c-pointer-passing-question/3246806#3246806). I'm surprised you deleted your answer. I don't think it's really wrong. I just think the issue is not as easy as it seems at first. The code I have seen the problem with was a multi-threaded real-time 3D scene renderer. That's not your every-day DB-backed reporting engine most people hack on. – sbi Jul 22 '10 at 17:25
  • Usually the performance bottleneck is elsewhere so I don't care. This is almost always premature and useless optimization. I just did some experiments with references, passing shared_ptr's by-value, moving etc and didn't notice any difference in the performance of a *real* application, not a benchmark. C++ is an academic language and always someone is telling you what you *should* do even though it doesn't matter at all. – juzzlin Jun 12 '19 at 15:39
  • The habit of passing shared_ptr by ref should not be followed in lambdas. If it gets destroyed elsewhere (passing by ref doesn't bump the ref count), your callback/lambda may crash. OTOH, passing it by value in lambdas too is dangerous and can cause memory leaks. Instead, we should pass `weak_ptr` to a shared_ptr. – Vishal Sahu Mar 30 '20 at 00:20

10 Answers10

253

This question has been discussed and answered by Scott, Andrei and Herb during Ask Us Anything session at C++ and Beyond 2011. Watch from 4:34 on shared_ptr performance and correctness.

Shortly, there is no reason to pass by value, unless the goal is to share ownership of an object (eg. between different data structures, or between different threads).

Unless you can move-optimise it as explained by Scott Meyers in the talk video linked above, but that is related to actual version of C++ you can use.

A major update to this discussion has happened during GoingNative 2012 conference's Interactive Panel: Ask Us Anything! which is worth watching, especially from 22:50.

Olivia Stork
  • 4,312
  • 4
  • 23
  • 39
mloskot
  • 33,165
  • 10
  • 97
  • 122
  • 6
    but as shown here it is cheaper to pass by value: http://stackoverflow.com/a/12002668/128384 shouldn't that be taken into account as well (at least for constructor arguments etc where a the shared_ptr is going to be made a member of the class)? – stijn Jan 08 '13 at 11:12
  • 2
    @stijn Yes and no. The Q&A you point is incomplete, unless it clarifies version of the C++ standard it refers to. It is very easy to spread general never/always rules which are simply misleading. Unless, readers take time to get familiar with David Abrahams article and references, or take posting date vs current C++ standard into account. So, both answers, mine and the one you pointed, is correct given the time of posting. – mloskot Jan 08 '13 at 12:14
  • agreed - I was asking because the question here is pretty old while your nswer is much more recent. Maybe you should state in the answer that it's more focused on pre C++11? – stijn Jan 08 '13 at 12:34
  • 1
    "_unless there is multi-threading_" no, MT is in no way special. – curiousguy Aug 13 '15 at 21:21
  • @curiousguy If multiple threads work on copies, yes, but if multiple threads write to mutable shared_ptr passed by reference, no thread-safety is guaranteed. Similarly to what's discussed here http://stackoverflow.com/a/11492094/151641 So, you may need to elaborate to avoid fuzzy discussion. – mloskot Aug 14 '15 at 09:19
  • @mloskot Multiple threads shouldn't write on the same smart ptr at the same time. This doesn't change the fact that the pass by ref/value is independent of MT. – curiousguy Aug 14 '15 at 10:30
  • 5
    I'm super late to the party, but my reason to want to pass shared_ptr by value is that it makes the code shorter and prettier. Seriously. `Value*` is short and readable, but it's bad, so now my code is full of `const shared_ptr&` and it's significantly less readable and just... less tidy. What used to be `void Function(Value* v1, Value* v2, Value* v3)` is now `void Function(const shared_ptr& v1, const shared_ptr& v2, const shared_ptr& v3)`, and people are okay with this? – Alex Jun 15 '16 at 09:45
  • 1
    **const** seems to be the way to go, not *just* reference. It makes the intent a lot clearer and makes it harder to screw up the code later on if the shared_ptr's counter needs to be modified. – Andrew Sep 11 '16 at 03:39
  • 11
    @Alex Common practice is creating aliases (typedefs) right after the class. For your example: `class Value {...}; using ValuePtr = std::shared_ptr;` Then your function becomes simpler: `void Function(const ValuePtr& v1, const ValuePtr& v2, const ValuePtr& v3)` and you get maximum performance. That's why you use C++, isn't it? :) – 4LegsDrivenCat Nov 23 '16 at 16:51
  • Yeah I've recently started to do that as well, I just do Value_ptr instead of ValuePtr. – Alex Nov 24 '16 at 11:25
  • I don't understand why you should pass a shared_ptr by value to a function if you only intend to share ownership (e.g., you're going to assign it to a member variable). Doing this will cause an unnecessary increment and decrement of the ref counter, since the function argument is considered a copy. Or am I missing something? – red.october Apr 07 '17 at 21:42
  • Wait, I just re-read the article again and I think I missed the part where it said you use std::move on the parameter afterwards to transfer ownership. Is the idea that if we accept shared_ptr by value, then it can either by constructed using the copy-constructor or the move-constructor depending on whether we are passed an l-value or r-value. The copy-constructor causes one copy, and the move-constructor causes no copies. Thus we will have *at most* one copy operation. If we accepted the shared_ptr by reference, then we would always have one copy operation? – red.october Apr 07 '17 at 22:03
  • This seems to be outdated now, by looking at [this post](https://herbsutter.com/2013/06/05/gotw-91-solution-smart-pointer-parameters/) from Herb Sutter – mbrt Feb 21 '18 at 12:58
  • @mbrt I see no contradiction between Sutter's GOTW article and the recommendation "there is no reason to pass by value". – mloskot Feb 21 '18 at 21:16
  • @mloskot I do see it: ... don’t pass by smart pointer unless you want to use/modify the smart pointer itself (like any object). The main thing is that pass by value/*/& are all still good and should still be used primarily. It’s just that we now have a couple of idioms for expressing ownership transfer in function signatures, notably passing a unique_ptr by value means “sink” and passing a shared_ptr by value means “gonna share ownership.” So there's a reason to do it – mbrt Feb 22 '18 at 12:17
  • @mbrt I understand your comments but that is different aspect of `shared_ptr` usage. Here I answer the very specific question: `shared_ptr`by value or reference? and I'm not extending the answer to "If by shared_ptr at all". – mloskot Feb 22 '18 at 22:02
  • @mloskot, maybe I'm not being clear. I refer to -- passing a `shared_ptr` by value means "gonna share ownership". This means there's a reason to pass a `shared_ptr` by value, which invalidates the statements "there is no reason to pass by value". – mbrt Feb 23 '18 at 12:58
  • @mbrt There is "unless" clause. I have made it a bit clearer. – mloskot Feb 23 '18 at 18:41
  • 1
    I still don't understand the unless clause: " unless the goal is to share ownership of an object" - isn't that always the case with a `shared_ptr`? Also, value semantics are more "natural". Passing by reference always requires justification, not the other way around. Why should we pass by reference? – einpoklum Mar 04 '18 at 21:28
  • 1
    @Alex the question is very specific: "When a function should take a `shared_ptr`...". But that is only the case when ownership of the pointer will be shared by making a copy inside the function and storing it for use after the function returns. If the function doesn't require ownership, a plain pointer gives you the flexibility to call it with either `shared_ptr.get()` or `unique_ptr.get()`. In my view a function taking a `shared_ptr` or `unique_ptr` implies that the function *requires* ownership. – Mark Ransom Mar 27 '18 at 22:36
  • @einpoklum I agree, there's no point using a `shared_ptr` unless you *need* to share ownership - doesn't matter how you pass it. As for pass by value being more natural, that's just an accident of history - modern code should prefer pass by const reference for efficiency's sake, unless you're passing a very simple type. – Mark Ransom Mar 27 '18 at 22:40
113

Here's Herb Sutter's take

Guideline: Don’t pass a smart pointer as a function parameter unless you want to use or manipulate the smart pointer itself, such as to share or transfer ownership.

Guideline: Express that a function will store and share ownership of a heap object using a by-value shared_ptr parameter.

Guideline: Use a non-const shared_ptr& parameter only to modify the shared_ptr. Use a const shared_ptr& as a parameter only if you’re not sure whether or not you’ll take a copy and share ownership; otherwise use widget* instead (or if not nullable, a widget&).

acel
  • 1,311
  • 1
  • 8
  • 7
  • 3
    Thanks for the link to Sutter. It is an excellent article. I disagree with him on widget*, preferring optional if C++14 is available. widget* is too ambiguous from old code. – Eponymous Jan 09 '15 at 15:57
  • 4
    +1 for including widget* and widget& as possibilities. Just to elaborate, passing widget* or widget& is probably the best option when the function is not examining/modifying the pointer object itself. The interface is more general, as it doesn't require a specific pointer type, and the performance issue of the shared_ptr reference count is dodged. – tgnottingham Sep 02 '15 at 19:33
  • 4
    I think this should be the accepted answer today, because of the second guideline. It clearly invalidates the current accepted answer, that says: there is no reason to pass by value. – mbrt Feb 23 '18 at 13:02
72

Personally I would use a const reference. There is no need to increment the reference count just to decrement it again for the sake of a function call.

Evan Teran
  • 80,654
  • 26
  • 169
  • 231
  • 1
    I did not down-vote your answer, but before this is a matter of preference, there are pros and cons to each of the two possibilities to consider. And it would be good to know and discuss theses pros and cons. Afterwards everyone can make a decision for himself. – Danvil Jul 28 '10 at 12:18
  • @Danvil: taking into consideration of how `shared_ptr` works, the only possible downside to not passing by reference is a slight loss in performance. There are two causes here. a) the pointer aliasing feature means to pointers worth of data plus a counter (maybe 2 for weak refs) is copied, so it is slightly more expensive to copy the data round. b) atomic reference counting is slightly slower than plain old increment/decrement code, but is needed in order to be thread safe. Beyond that, the two methods are the same for most intents and purposes. – Evan Teran Jul 29 '10 at 02:02
40

Pass by const reference, it's faster. If you need to store it, say in some container, the ref. count will be auto-magically incremented by the copy operation.

Nikolai Fetissov
  • 77,392
  • 11
  • 105
  • 164
  • 4
    Downvote becouse its opinion without any numbers to back it up. – kwesolowski Feb 15 '18 at 15:32
  • 1
    @kwesolowski The answer provides the analytical reason about why const reference is faster (i.e., no unnecessary ref-count increment/decrement). It is an alternative to benchmarking. – Ali Nov 29 '20 at 13:45
27

I ran the code below, once with foo taking the shared_ptr by const& and again with foo taking the shared_ptr by value.

void foo(const std::shared_ptr<int>& p)
{
    static int x = 0;
    *p = ++x;
}

int main()
{
    auto p = std::make_shared<int>();
    auto start = clock();
    for (int i = 0; i < 10000000; ++i)
    {
        foo(p);
    }    
    std::cout << "Took " << clock() - start << " ms" << std::endl;
}

Using VS2015, x86 release build, on my intel core 2 quad (2.4GHz) processor

const shared_ptr&     - 10ms  
shared_ptr            - 281ms 

The copy by value version was an order of magnitude slower.
If you are calling a function synchronously from the current thread, prefer the const& version.

tcb
  • 3,612
  • 3
  • 26
  • 48
  • 1
    Can you say what compiler, platform, and optimization settings you used? – Carlton Aug 26 '15 at 13:28
  • I used the debug build of vs2015, updated the answer to use the release build now. – tcb Aug 26 '15 at 13:53
  • 1
    I'm curious if when optimisation is turned on, you get the same results with both – Elliot Woods Jan 18 '16 at 11:39
  • Using `clang++ -O3 -std=c++11` on my 2012 MacBook Pro (2.5 GHz Intel Core i7) I get 42 ms and 179227 ms respectively. – wcochran Oct 16 '17 at 16:22
  • 2
    Optimization does not help much. the problem is lock contention on the reference count on the copy. – Alexander Oh Mar 27 '18 at 23:25
  • 1
    That's not the point. Such a `foo()` function should not even accept a shared pointer in the first place because it's not using this object: it should accept a `int&` and do `p = ++x;`, calling `foo(*p);` from `main()`. A function accepts a smart pointer object when it needs to do something with it, and most of the time, what you need to do is moving it (`std::move()`) to somewhere else, so a by-value parameter has no cost. – eepp Oct 22 '18 at 15:37
  • This result might be true, but this is not a very realistic application. According to my tests this doesn't matter at all in real world applications - unless doing something weird like tight loops with millions of shared_ptr's and nothing else like in this benchmark. And still the difference is in millisecond class. In my applications the performance bottleneck is elsewhere. – juzzlin Jun 12 '19 at 15:33
16

Since C++11 you should take it by value over const& more often than you might think.

If you are taking the std::shared_ptr (rather than the underlying type T), then you are doing so because you want to do something with it.

If you would like to copy it somewhere, it makes more sense to take it by copy, and std::move it internally, rather than taking it by const& and then later copying it. This is because you allow the caller the option to in turn std::move the shared_ptr when calling your function, thus saving yourself a set of increment and decrement operations. Or not. That is, the caller of the function can decide whether or not he needs the std::shared_ptr around after calling the function, and depending on whether or not move or not. This is not achievable if you pass by const&, and thus it is then preferably to take it by value.

Of course, if the caller both needs his shared_ptr around for longer (thus can not std::move it) and you don't want to create a plain copy in the function (say you want a weak pointer, or you only sometimes want to copy it, depending on some condition), then a const& might still be preferable.

For example, you should do

void enqueue(std::shared<T> t) m_internal_queue.enqueue(std::move(t));

over

void enqueue(std::shared<T> const& t) m_internal_queue.enqueue(t);

Because in this case you always create a copy internally

Cookie
  • 10,754
  • 13
  • 47
  • 69
  • People such as Jason Turner are now saying though that profligate use of `std::move` is a "code smell" and it should be avoided if possible. – Den-Jason Sep 03 '20 at 02:59
10

There was a recent blog post: https://medium.com/@vgasparyan1995/pass-by-value-vs-pass-by-reference-to-const-c-f8944171e3ce

So the answer to this is: Do (almost) never pass by const shared_ptr<T>&.
Simply pass the underlying class instead.

Basically the only reasonable parameters types are:

  • shared_ptr<T> - Modify and take ownership
  • shared_ptr<const T> - Don't modify, take ownership
  • T& - Modify, no ownership
  • const T& - Don't modify, no ownership
  • T - Don't modify, no ownership, Cheap to copy

As @accel pointed out in https://stackoverflow.com/a/26197326/1930508 the advice from Herb Sutter is:

Use a const shared_ptr& as a parameter only if you’re not sure whether or not you’ll take a copy and share ownership

But in how many cases are you not sure? So this is a rare situation

Flamefire
  • 3,954
  • 2
  • 22
  • 50
2

Not knowing time cost of shared_copy copy operation where atomic increment and decrement is in, I suffered from much higher CPU usage problem. I never expected atomic increment and decrement may take so much cost.

Following my test result, int32 atomic increment and decrement takes 2 or 40 times than non-atomic increment and decrement. I got it on 3GHz Core i7 with Windows 8.1. The former result comes out when no contention occurs, the latter when high possibility of contention occurs. I keep in mind that atomic operations are at last hardware based lock. Lock is lock. Bad to performance when contention occurs.

Experiencing this, I always use byref(const shared_ptr&) than byval(shared_ptr).

Hyunjik Bae
  • 2,183
  • 2
  • 17
  • 29
2

It's known issue that passing shared_ptr by value has a cost and should be avoided if possible.

The cost of passing by shared_ptr

Most of the time passing shared_ptr by reference, and even better by const reference, would do.

The cpp core guideline has a specific rule for passing shared_ptr

R.34: Take a shared_ptr parameter to express that a function is part owner

void share(shared_ptr<widget>);            // share -- "will" retain refcount

An example of when passing shared_ptr by value is really necessary is when the caller passes a shared object to an asynchronous callee - ie the caller goes out of scope before the callee completes its job. The callee must "extend" the lifetime of the shared object by taking a share_ptr by value. In this case, passing a reference to shared_ptr won't do.

The same goes for passing a shared object to a work thread.

artm
  • 16,141
  • 4
  • 27
  • 46
-5

shared_ptr isn't large enough, nor do its constructor\destructor do enough work for there to be enough overhead from the copy to care about pass by reference vs pass by copy performance.

stonemetal
  • 5,972
  • 21
  • 25
  • 2
    @stonemetal: What about atomic instructions during creating new shared_ptr? – Quarra Oct 22 '14 at 14:37
  • It's a non-POD type, so in most ABIs even passing it "by value" actually passes a pointer. It's not the actual copying of bytes that's the issue at all. As you can see in the asm output, passing a `shared_ptr` by value takes over 100 x86 instructions (including expensive `lock`ed instructions to atomically inc/dec the ref count). Passing by constant ref is the same as passing a pointer to anything (and in this example on the Godbolt compiler explorer, tail-call optimization turns this into a simple jmp instead of a call: https://godbolt.org/g/TazMBU). – Peter Cordes Oct 04 '16 at 20:05
  • TL:DR: This is C++ where copy-constructors can do a lot more work than just copying the bytes. This answer is total garbage. – Peter Cordes Oct 04 '16 at 20:07
  • 2
    http://stackoverflow.com/questions/3628081/shared-ptr-horrible-speed As an example Shared pointers passed by value vs pass by reference he sees a run time difference of approximately 33%. If you are working on performance critical code then naked pointers get you a bigger performance increase. So sure pass by const ref if you remember to but it isn't a big deal if you don't. It is much more important to not use shared_ptr if you don't need it. – stonemetal Nov 09 '16 at 20:08