[c++] 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:

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

This question is related to c++ c++11 boost shared-ptr

The answer is


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.


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.


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.


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


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.


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


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&).


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


Examples related to c++

Method Call Chaining; returning a pointer vs a reference? How can I tell if an algorithm is efficient? Difference between opening a file in binary vs text How can compare-and-swap be used for a wait-free mutual exclusion for any shared data structure? Install Qt on Ubuntu #include errors detected in vscode Cannot open include file: 'stdio.h' - Visual Studio Community 2017 - C++ Error How to fix the error "Windows SDK version 8.1" was not found? Visual Studio 2017 errors on standard headers How do I check if a Key is pressed on C++

Examples related to c++11

Remove from the beginning of std::vector Converting std::__cxx11::string to std::string What exactly is std::atomic? C++ How do I convert a std::chrono::time_point to long and back Passing capturing lambda as function pointer undefined reference to 'std::cout' Is it possible to use std::string in a constexpr? How does #include <bits/stdc++.h> work in C++? error::make_unique is not a member of ‘std’ no match for ‘operator<<’ in ‘std::operator

Examples related to boost

CMake is not able to find BOOST libraries version `CXXABI_1.3.8' not found (required by ...) Already defined in .obj - no double inclusions C++ Boost: undefined reference to boost::system::generic_category() fatal error LNK1104: cannot open file 'libboost_system-vc110-mt-gd-1_51.lib' How to install Boost on Ubuntu Calculate rolling / moving average in C++ undefined reference to boost::system::system_category() when compiling Calculate mean and standard deviation from a vector of samples in C++ using Boost Get current time in milliseconds using C++ and Boost

Examples related to shared-ptr

Difference in make_shared and normal shared_ptr in C++ When is std::weak_ptr useful? Error: expected type-specifier before 'ClassName' Differences between unique_ptr and shared_ptr Example to use shared_ptr? Should we pass a shared_ptr by reference or by value? Where is shared_ptr? When to use virtual destructors?