Reputation: 23031
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.
Upvotes: 394
Views: 199721
Reputation: 51
Question: by reference or by value?
Answer: different needs, different chooses.
Microsoft explains it very clearly in this article: "How to: Create and Use shared_ptr instances(Example 5)":
Pass the shared_ptr by value. This invokes the copy constructor, increments the reference count, and makes the callee an owner. There's a small amount of overhead in this operation, which may be significant depending on how many shared_ptr objects you're passing. Use this option when the implied or explicit code contract between the caller and callee requires that the callee be an owner.
Pass the shared_ptr by reference or const reference. In this case, the reference count isn't incremented, and the callee can access the pointer as long as the caller doesn't go out of scope. Or, the callee can decide to create a shared_ptr based on the reference, and become a shared owner. Use this option when the caller has no knowledge of the callee, or when you must pass a shared_ptr and want to avoid the copy operation for performance reasons.
Pass the underlying pointer or a reference to the underlying object. This enables the callee to use the object, but doesn't enable it to share ownership or extend the lifetime. If the callee creates a shared_ptr from the raw pointer, the new shared_ptr is independent from the original, and doesn't control the underlying resource. Use this option when the contract between the caller and callee clearly specifies that the caller retains ownership of the shared_ptr lifetime.
When you're deciding how to pass a shared_ptr, determine whether the callee has to share ownership of the underlying resource. An "owner" is an object or function that can keep the underlying resource alive for as long as it needs it. If the caller has to guarantee that the callee can extend the life of the pointer beyond its (the function's) lifetime, use the first option. If you don't care whether the callee extends the lifetime, then pass by reference and let the callee copy it or not.
If you have to give a helper function access to the underlying pointer, and you know that the helper function uses the pointer and return before the calling function returns, then that function doesn't have to share ownership of the underlying pointer. It just has to access the pointer within the lifetime of the caller's shared_ptr. In this case, it's safe to pass the shared_ptr by reference, or pass the raw pointer or a reference to the underlying object. Passing this way provides a small performance benefit, and may also help you express your programming intent.
Sometimes, for example in a std::vector<shared_ptr>, you may have to pass each shared_ptr to a lambda expression body or named function object. If the lambda or function doesn't store the pointer, then pass the shared_ptr by reference to avoid invoking the copy constructor for each element.
Upvotes: 2
Reputation: 38940
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.
Upvotes: 306
Reputation: 17713
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.
Upvotes: 7
Reputation: 5807
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 ownershipshared_ptr<const T>
- Don't modify, take ownershipT&
- Modify, no ownershipconst T&
- Don't modify, no ownershipT
- Don't modify, no ownership, Cheap to copyAs @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
Upvotes: 40
Reputation: 12692
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
Upvotes: 26
Reputation: 2949
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).
Upvotes: 2
Reputation: 4604
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.
Upvotes: 37
Reputation: 1761
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&).
Upvotes: 158
Reputation: 90523
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.
Upvotes: 85
Reputation: 6208
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.
Upvotes: -6
Reputation: 84239
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.
Upvotes: 51