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
or by value: void foo(shared_ptr
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.
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!)
shared_ptr
class member. Do you do it by const-refs?
weak_ptr
to a shared_ptr.
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.
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&).
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.
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.
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.
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.
clang++ -O3 -std=c++11
on my 2012 MacBook Pro (2.5 GHz Intel Core i7) I get 42 ms and 179227 ms respectively.
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.
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
std::move
is a "code smell" and it should be avoided if possible.
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
shared_ptr
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
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.
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).
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.
shared_ptr<int>
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: godbolt.org/g/TazMBU).
Success story sharing
Value*
is short and readable, but it's bad, so now my code is full ofconst shared_ptr<Value>&
and it's significantly less readable and just... less tidy. What used to bevoid Function(Value* v1, Value* v2, Value* v3)
is nowvoid Function(const shared_ptr<Value>& v1, const shared_ptr<Value>& v2, const shared_ptr<Value>& v3)
, and people are okay with this?class Value {...}; using ValuePtr = std::shared_ptr<Value>;
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? :)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?