ChatGPT解决这个技术问题 Extra ChatGPT

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

or by value: void foo(shared_ptr 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.

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!)
Hey i would be interested in your guys opinion about returning a shared_ptr class member. Do you do it by const-refs?
Third possibility is to use std::move() with C++0x, this swaps both shared_ptr
@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.
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.

O
Olivia Stork

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.


but as shown here it is cheaper to pass by value: 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 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.
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<Value>& 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<Value>& v1, const shared_ptr<Value>& v2, const shared_ptr<Value>& v3), and people are okay with this?
@Alex Common practice is creating aliases (typedefs) right after the class. For your example: 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? :)
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?
a
acel

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


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.
+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.
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.
E
Evan Teran

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 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: 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.
N
Nikolai Fetissov

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.


Downvote becouse its opinion without any numbers to back it up.
@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.
t
tcb

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.


Can you say what compiler, platform, and optimization settings you used?
I'm curious if when optimisation is turned on, you get the same results with both
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.
Optimization does not help much. the problem is lock contention on the reference count on the copy.
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.
C
Cookie

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


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.
F
Flamefire

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 - Modify and take ownership

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


IMO this is one of the correct, but the most succinct answer.
a
artm

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.


H
Hyunjik Bae

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


s
stonemetal

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: What about atomic instructions during creating new shared_ptr?
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<int> by value takes over 100 x86 instructions (including expensive locked 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).
TL:DR: This is C++ where copy-constructors can do a lot more work than just copying the bytes. This answer is total garbage.
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.