Danvil
Danvil

Reputation: 23031

Should we pass a shared_ptr by reference or by value?

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

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

Answers (11)

wking
wking

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

mloskot
mloskot

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

tdao
tdao

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

Flamefire
Flamefire

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 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

Upvotes: 40

Cookie
Cookie

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

Hyunjik Bae
Hyunjik Bae

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

tcb
tcb

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

acel
acel

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

Evan Teran
Evan Teran

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

stonemetal
stonemetal

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

Nikolai Fetissov
Nikolai Fetissov

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

Related Questions