Passing const shared_ptr<T>& versus just shared_ptr<T> as parameter - c++

I've been reading quite a number of discussions about performance issues when smart pointers are involved in an application. One of the frequent recommendations is to pass a smart pointer as const& instead of a copy, like this:
void doSomething(std::shared_ptr<T> o) {}
versus
void doSomething(const std::shared_ptr<T> &o) {}
However, doesn't the second variant actually defeat the purpose of a shared pointer? We are actually sharing the shared pointer here, so if for some reasons the pointer is released in the calling code (think of reentrancy or side effects) that const pointer becomes invalid. A situation the shared pointer actually should prevent. I understand that const& saves some time as there is no copying involved and no locking to manage the ref count. But the price is making the code less safe, right?

The advantage of passing the shared_ptr by const& is that the reference count doesn't have to be increased and then decreased. Because these operations have to be thread-safe, they can be expensive.
You are quite right that there is a risk that you can have a chain of passes by reference that later invalidates the head of the chain. This happened to me once in a real-world project with real-world consequences. One function found a shared_ptr in a container and passed a reference to it down a call stack. A function deep in the call stack removed the object from the container, causing all the references to suddenly refer to an object that no longer existed.
So when you pass something by reference, the caller must ensure it survives for the life of the function call. Don't use a pass by reference if this is an issue.
(I'm assuming you have a use case where there's some specific reason to pass by shared_ptr rather than by reference. The most common such reason would be that the function called may need to extend the life of the object.)
Update: Some more details on the bug for those interested: This program had objects that were shared and implemented internal thread safety. They were held in containers and it was common for functions to extend their lifetimes.
This particular type of object could live in two containers. One when it was active and one when it was inactive. Some operations worked on active objects, some on inactive objects. The error case occurred when a command was received on an inactive object that made it active while the only shared_ptr to the object was held by the container of inactive objects.
The inactive object was located in its container. A reference to the shared_ptr in the container was passed, by reference, to the command handler. Through a chain of references, this shared_ptr ultimately got to the code that realized this was an inactive object that had to be made active. The object was removed from the inactive container (which destroyed the inactive container's shared_ptr) and added to the active container (which added another reference to the shared_ptr passed to the "add" routine).
At this point, it was possible that the only shared_ptr to the object that existed was the one in the inactive container. Every other function in the call stack just had a reference to it. When the object was removed from the inactive container, the object could be destroyed and all those references were to a shared_ptr that that no longer existed.
It took about a month to untangle this.

First of all, don't pass a shared_ptr down a call chain unless there is a possibility that one of the called functions will store a copy of it. Pass a reference to the referred object, or a raw pointer to that object, or possibly a box, depending on whether it can be optional or not.
But when you do pass a shared_ptr, then preferably pass it by reference to const, because copying a shared_ptr has additional overhead. The copying must update the shared reference count, and this update must be thread safe. Hence there is a little inefficiency that can be (safely) avoided.
Regarding
” the price is making the code less safe, right?
No. The price is an extra indirection in naïvely generated machine code, but the compiler manages that. So it's all about just avoiding a minor but totally needless overhead that the compiler can't optimize away for you, unless it's super-smart.
As David Schwarz exemplified in his answer, when you pass by reference to const the aliasing problem, where the function you call in turn changes or calls a function that changes the original object, is possible. And by Murphy's law it will happen at the most inconvenient time, at maximum cost, and with the most convoluted impenetrable code. But this is so regardless of whether the argument is a string or a shared_ptr or whatever. Happily it's a very rare problem. But do keep it in mind, also for passing shared_ptr instances.

First of all there is a semantic difference between the two:
passing shared pointer by value indicates your function is going to take its part of the underlying object ownership.
Passing shared_ptr as const reference does not indicate any intent on top of just passing the underlying object by const reference (or raw pointer) apart from inforcing users of this function to use shared_ptr. So mostly rubbish.
Comparing performance implications of those is irrelevant as long as they are semantically different.
from https://herbsutter.com/2013/06/05/gotw-91-solution-smart-pointer-parameters/
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.
and this time I totally agree with Herb :)
And another quote from the same, which answers the question more directly
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 * instead (or if not nullable, a &)

As pointed out in C++ - shared_ptr: horrible speed, copying a shared_ptr takes time. The construction involves an atomic increment and the destruction an atomic decrement, an atomic update (whether increment or decrement) may prevent a number of compiler optimizations (memory loads/stores cannot migrate across the operation) and at hardware level involves the CPU cache coherency protocol to ensure that the whole cache line is owned (exclusive mode) by the core doing the modification.
So, you are right, std::shared_ptr<T> const& may be used as a performance improvement over just std::shared_ptr<T>.
You are also right that there is a theoretical risk for the pointer/reference to become dangling because of some aliasing.
That being said, the risk is latent in any C++ program already: any single use of a pointer or reference is a risk. I would argue that the few occurrences of std::shared_ptr<T> const& should be a drop in the water compared to the total number of uses of T&, T const&, T*, ...
Lastly, I would like to point that passing a shared_ptr<T> const& is weird. The following cases are common:
shared_ptr<T>: I need a copy of the shared_ptr
T*/T const&/T&/T const&: I need a (possibly null) handle to T
The next case is much less common:
shared_ptr<T>&: I may reseat the shared_ptr
But passing shared_ptr<T> const&? Legitimate uses are very very rare.
Passing shared_ptr<T> const& where all you want is a reference to T is an anti-pattern: you force the user to use shared_ptr when they could be allocating T another way! Most of the times (99,99..%), you should not care how T is allocated.
The only case where you would pass a shared_ptr<T> const& is if you are not sure whether you will need a copy or not, and because you have profiled the program and showed that this atomic increment/decrement was a bottleneck you have decided to defer the creation of the copy to only the cases where it is needed.
This is such an edge case that any use of shared_ptr<T> const& should be viewed with the highest degree of suspicion.

If no modification of ownership is involved in your method, there's no benefit for your method to take a shared_ptr by copy or by const reference, it pollutes the API and potentially incur overhead (if passing by copy)
The clean way is to pass the underlying type by const ref or ref depending of your use case
void doSomething(const T& o) {}
auto s = std::make_shared<T>(...);
// ...
doSomething(*s);
The underlying pointer can't be released during the method call

I think its perfectly reasonable to pass by const & if the target function is synchronous and only makes use of the parameter during execution, and has no further need of it upon return. Here it is reasonable to save on the cost of increasing the reference count - as you don't really need the extra safety in these limited circumstances - provided you understand the implications and are sure the code is safe.
This is as opposed to when the function needs to save the parameter (for example in a class member) for later re-reference.

Related

Returning reference to potentially changing data in C++

In C++, is it a good idea to return a constant reference to data that may change? For instance, suppose you have a functor:
template<class T>
struct f {
f(const T& init) data{init} {}
const T& operator() () { return ++data; }
private:
T data;
};
As an example of my concerns, could a function take the address of the returned data? This would give a nasty surprise later when the value was changed "behind the client's back." (Or does the returned reference count as an rvalue, making finding its address illegal?) What other issues might I, or other clients, run into down the road?
tl;dr: Is the above functor a good idea?
could a function take the address of the returned data?
Yes. i.e. const int* ptr = &my_f(); Please note that you'll still safe from having the client inadvertently modifying the data and break your class invariants, as you cannot modify the data pointed by a const int*, except if you start outwitting the type system and cast it to an int*.
This would give a nasty surprise later when the value was changed "behind the client's back."
The client would know based from the function's signature alone that the function returns a reference. If clients want to have a copy of their own for them to modify or to prevent it from being modified by your class, then he can just do int my_own_copy = my_f();, and he gets a copy.
Or does it count as an rvalue, making finding its address illegal?
Unless you do something like const int& operator() () { return data++; /* Note: postfix */ }, then the client would be safe from having to illegally reference invalid data.
What other issues might I, or other clients, run into down the road?
One common problem would be that the lifetime of the client's reference would exceed the lifetime of the object that has the referenced data. Still, this is still true for other references, and thus the client doesn't have to learn another guideline for their references to remain valid.
tl;dr: Is the above functor a good idea?
tl;dr: Yes. As mentioned above, the client could still get a copy if he needs it, or he may opt to have a reference to the data if he doesn't want to incur the cost of copying potentially large data.
For primitive types, returning by const reference does usually not really make sense - since the reference will be equally big (in terms of memory size), or might be even larger than the primitive type itself.
Also, consider the case where an instance of struct f gets deleted while a reference is still in use - it will refer to deleted data! That is never good and needs to be prevented.
Only for larger, user-defined types I would recommend to consider this pattern (where avoiding copying might give a performance advantage or can help maintain a small memory footprint), but then also only use it when you can guarantee that the object returning the reference will live longer or at least just as long than the object using the reference.
Regarding the caller being able to modify the value: Only taking the address of such an expression is not enough; the caller would also have to const-cast the reference to make it modifyable (check the referene for const_cast). Except for some rare circumstances, the const_cast is very bad practice, and should therefore usually cause all alarm bells to ring during e.g. a review anyway. So that aspect is not something you'd have to consider too much.

Passing smart-pointers by reference

Smart-pointers are generally tiny so passing by value isn't a problem, but is there any problem passing references to them; or rather are there specific cases where this mustn't be done?
I'm writing a wrapper library and several of my classes wrap smart-pointer objects in the underlying library... my classes are not smart-pointers but the APIs currently pass smart-pointer objects by value.
e.g current code:
void class::method(const AnimalPtr pAnimal) { ... }
becomes
void class::method(const MyAnimal &animal){...}
where MyAnimal is my new wrapper class encapsulating AnimalPtr.
There is no guarantee the Wrapper classes won't one day grow beyond wrapping a smart-pointer, so passing by value makes me nervous.
You should pass shared pointers by reference, not value, in most cases. While the size of a std::shared_ptr is small, the cost of copying involves an atomic operation (conceptually an atomic increment and an atomic decrement on destruction of the copy, although I believe that some implementations manage to do a non-atomic increment).
In other cases, for example std::unique_ptr you might prefer to pass by value, as the copy will have to be a move and it clearly documents that ownership of the object is transferred to the function (if you don't want to transfer ownership, then pass a reference to the real object, not the std::unique_ptr).
In other cases your mileage might vary. You need to be aware of what the semantics of copy are for your smart pointer, and whether you need to pay for the cost or not.
It's ok to pass a smart pointer by reference, except if it's to a constructor. In a constructor, it's possible to store a reference to the original object, which violates the contract of the smart pointers. You would likely get memory corruption if you did that. Even if your constructor does not today store the reference, I would still be wary because code changes and it's an easy thing to miss if you decide later you need to hold the variable longer.
In a normal function, you cannot store a function parameter as a reference anywhere because references must be set during their initialization. You could assign the reference to some longer-living non-reference variable, but that would be a copy and so would increase its lifetime appropriately. So in either case, you could not hold onto it past when the calling function might have freed it. In this case, you might get a small performance boost with a reference, but I wouldn't plan on noticing it in most cases.
So I would say - constructor, always pass by value; other functions, pass by reference if you want.

Missing equality between shared_ptr and weak_ptr

While I do understand why there is no operator== for shared_ptr and unique_ptr, I wonder why there is none for shared_ptr and weak_ptr. Especially since you can create a weak_ptr via a reference on shared_ptr.
I would assume that for 99% of the time you want lhs.get() == rhs.get(). I would now go forward and introduce that into my code unless someone can name me a good reason, why one should not do such a thing.
weak_ptr doesn' have a get() method because you need to explicitly lock the weak_ptr before you can access the underlying pointer. Making this explicit is a deliberate design decision. If the conversion were implicit it would be very easy to write code that would be unsafe if the last shared_ptr to the object were to be destroyed while the underlying pointer obtained from the weak_ptr was still being examined.
This boost page has a good description of the pitfalls and why weak_ptr has such a limited interface.
If you need to do a quick comparison, then you can do shared == weak.lock(). If the comparison is true then you know that weak must still be valid as you hold a separate shared_ptr to the same object. There is no such guarantee if the comparison returns false.
Because it has a cost.
A weak_ptr is like an observer, not a real pointer. To do any work with it you first need to obtain a shared_ptr from it using its lock() method.
This has the effect of acquiring ownership, but it as costly as copying a regular shared_ptr (count increment, etc...) so it is nothing trivial.
As such, by not providing ==, you are forced to step back and actually check whether you really need this or not.
As the other answers have pointed out, simply comparing the underlying pointers would be perilous. For one, consider the following scenario: a weak reference A exists to an object, which is subsequently deleted, and therefore the weak reference expires. Then, another object is allocated in the memory freed up by said deletion, which has the same address. Now the underlying pointers are the same, even though the weak pointer originally referred to a different object!
As the other answers have suggested, one way is to compare shared == weak.lock(). Since lock() will return nullptr (and not some bogus pointer) if the weak pointer expired, his works for identifying if they are equal (as long as shared != nullptr). However, there are two problems with this:
It stops working when the weak pointer expires, in which case the comparison changes; after the expiration, it will only return true if shared == nullptr. This can be dangerous in cases where the comparison must remain stable, such as when using it as a key in an unordered_map or unordered_set.
lock() is a relatively expensive operation.
Fortunately, there is a better way to do this. Both weak_ptr and shared_ptr also store a pointer to what is known as the control block, which is what stores the reference counts and outlives the original object for as long as references remain to it. To check whether they refer to the same object, all we need to do is compare the control block pointers. This can be done with the owner_before method:
template<class T>
bool owner_equals(std::shared_ptr<T> &lhs, std::weak_ptr<T> &rhs) {
return !lhs.owner_before(rhs) && !rhs.owner_before(lhs);
}
This approach will even work for comparing two std::weak_ptrs with each other, if you wish to know whether they (once) referred to the same object, since the control block will last (at least) as long as all of the weak references.
Do keep in mind that this may not produce the expected result if you are using the aliasing feature of std::shared_ptr, which is a feature that lets you create two std::shared_ptr instances with the same control block that nonetheless store different pointers.

Can I cast shared_ptr<T> & to shared_ptr<T const> & without changing use_count?

I have a program that uses boost::shared_ptrs and, in particular, relies on the accuracy of the use_count to perform optimizations.
For instance, imagine an addition operation with two argument pointers called lhs and rhs. Say they both have the type shared_ptr<Node>. When it comes time to perform the addition, I'll check the use_count, and if I find that one of the arguments has a reference count of exactly one, then I'll reuse it to perform the operation in place. If neither argument can be reused, I must allocate a new data buffer and perform the operation out-of-place. I'm dealing with enormous data structures, so the in-place optimization is very beneficial.
Because of this, I can never copy the shared_ptrs without reason, i.e., every function takes the shared_ptrs by reference or const reference to avoid distorting use_count.
My question is this: I sometimes have a shared_ptr<T> & that I want to cast to shared_ptr<T const> &, but how can I do it without distorting the use count? static_pointer_cast returns a new object rather than a reference. I'd be inclined to think that it would work to just cast the whole shared_ptr, as in:
void f(shared_ptr<T> & x)
{
shared_ptr<T const> & x_ = *reinterpret_cast<shared_ptr<T const> *>(&x);
}
I highly doubt this complies with the standard, but, as I said, it will probably work. Is there a way to do this that's guaranteed safe and correct?
Updating to Focus the Question
Critiquing the design does not help answer this post. There are two interesting questions to consider:
Is there any guarantee (by the writer of boost::shared_ptr, or by the standard, in the case of std::tr1::shared_ptr) that shared_ptr<T> and shared_ptr<T const> have identical layouts and behavior?
If (1) is true, then is the above a legal use of reinterpret_cast? I think you would be hard-pressed to find a compiler that generates failing code for the above example, but that doesn't mean it's legal. Whatever your answer, can you find support for it in the C++ standard?
I sometimes have a shared_ptr<T> & that I want to cast to shared_ptr<T const> &, but how can I do it without distorting the use count?
You don't. The very concept is wrong. Consider what happens with a naked pointer T* and const T*. When you cast your T* into a const T*, you now have two pointers. You don't have two references to the same pointer; you have two pointers.
Why should this be different for smart pointers? You have two pointers: one to a T, and one to a const T. They're both sharing ownership of the same object, so you are using two of them. Your use_count therefore ought to be 2, not 1.
Your problem is your attempt to overload the meaning of use_count, co-opting its functionality for some other purpose. In short: you're doing it wrong.
Your description of what you do with shared_ptrs who's use_count is one is... frightening. You're basically saying that certain functions co-opt one of its arguments, which the caller is clearly using (since the caller obviously is still using it). And the caller doesn't know which one was claimed (if any), so the caller has no idea what the state of the arguments is after the function. Modifying the arguments for operations like that is usually not a good idea.
Plus, what you're doing can only work if you pass shared_ptr<T> by reference, which itself isn't a good idea (like regular pointers, smart pointers should almost always be taken by value).
In short, you're taking a very commonly used object with well-defined idioms and semantics, then requiring that it be used in a way that they are almost never used, with specialized semantics that work counter to the way everyone actually uses them. That's not a good thing.
You have effectively created the concept of co-optable pointer, a shared pointer that can be in 3 use states: empty, in use by the person who gave it to you only and thus you can steal from it, and in use by more than one person so you can't have it. It's not the semantics that shared_ptr exists to support. So you should write your own smart pointer that provides these semantics in a much more natural way.
Something that recognizes the difference between how many instances of a pointer you have around and how many actual users of it you have. That way, you can pass it around by value properly, but you have some way of saying that you are currently using it and don't want one of these other functions to claim it. It could use shared_ptr internally, but it should provide its own semantics.
static_pointer_cast is the right tool for the job — you've already identified that.
The problem with it isn't that it returns a new object, but rather that it leaves the old object unchanged. You want to get rid of the non-const pointer and move on with the const pointer. What you really want is static_pointer_cast< T const >( std::move( old_ptr ) ). But there isn't an overload for rvalue references.
The workaround is simple: manually invalidate the old pointer just as std::move would.
auto my_const_pointer = static_pointer_cast< T const >( modifiable_pointer );
modifiable_pointer = nullptr;
It might be slightly slower than reinterpret_cast, but it's a lot more likely to work. Don't underestimate how complex the library implementation is, and how it can fail when abused.
An aside: use pointer.unique() instead of use_count() == 1. Some implementations might use a linked list with no cached use count, making use_count() O(N) whereas the unique test remains O(1). The Standard recommends unique for copy on write optimization.
EDIT: Now I see you mention
I can never copy the shared_ptrs without reason, i.e., every function takes the shared_ptrs by reference or const reference to avoid distorting use_count.
This is Doing It Wrong. You've added another layer of ownership semantics atop what shared_ptr already does. They should be passed by value, with std::move used where the caller no longer desires ownership. If the profiler says you're spending time adjusting reference counts, then you might add some references-to-pointer in the inner loops. But as a general rule, if you can't set a pointer to nullptr because you're no longer using it, but someone else might be, then you've really lost track of ownership.
If you cast a shared_ptr to a different type, without changing the reference count, this implies that you'll now have two pointers to the same data. Hence, unless you erase the old pointer, you can't do this with shared_ptrs without "distorting the reference count".
I would suggest that you use raw pointers here instead, rather than going out of your way to not use the features of shared_ptrs. If you need to sometimes create new references, use enable_shared_from_this to derive a new shared_ptr to an existing raw pointer.
When it comes time to perform the addition, I'll check the use_count, and if I find that one of the arguments has a reference count of exactly one, then I'll reuse it to perform the operation in place.
This isn't necessarily valid unless you're applying some other rules across the whole program to make it so. Consider:
shared_ptr<Node> add(shared_ptr<Node> const &lhs,shared_ptr<Node> const &rhs) {
if(lhs.use_count()==1) {
// do whatever, reusing lhs
return lhs;
}
if(rhs.use_count()==1) {
// do whatever, reusing rhs
return rhs;
}
shared_ptr<Node> new_node = ... // do whatever without reusing lhs or rhs
return new_node;
}
void foo() {
shared_ptr<Node> a,b;
shared_ptr<Node> c = add(a,b);
// error, we still have a and b, and expect that they're unchanged! they could have been modified!
}
Instead if you take the smart pointers by value:
shared_ptr<Node> add(shared_ptr<Node> lhs,shared_ptr<Node> rhs) {
And the use_count()==1 then you know that your copy is the only one and it should be safe to reuse it.
However, there's a problem in using this as an optimization, because copying a shared_ptr requires synchronization. It could well be that doing all this synchronization all over the place costs far more than you save by reusing existing shared_ptrs. All this synchronization is the reason it's recommended that code that does not take ownership of a shared_ptr should take the shared_ptr by reference instead of by value.

How to pass a shared_ptr to a mutable object as a parameter?

I want to pass an object by smart-pointer reference to a function. The function may change the value of the referenced object, but may not change the reference itself. There are two obvious ways to handle this.
The first approach to pass the shared_ptr by value - it is the reference, so doesn't itself need to be passed by reference. The apparent issue with this is copying of the reference, which suggests some reference-counting overhead.
void foo (shared_ptr<bar> p)
The second approach is to pass the shared_ptr by const reference - avoiding the copying of the shared_ptr instance, but instead implying that accesses to the referenced object need two layers of dereferencing instead of one.
void foo (const shared_ptr<bar> &p)
In practice, these theoretical overheads will normally be trivial and irrelevant. Which suggests to me that instead of choosing one approach or the other for each individual case, I should almost always follow some standard convention. Which leads to the question...
Is there a standard convention for which of these approaches I should normally choose? If so, which is the conventional choice?
EDIT - Probably worth mentioning - one reason to consider the pass-by-const-reference case is because there's a pre-existing convention that most class/struct instances are passed by const-reference rather than by value, and shared_ptr is a class. Of course it isn't a heavyweight class (the cost of copying is small), so the reasons behind that older convention may not apply.
Always pass shared_ptrs by value. If you pass a reference, you might run into the problem that a call to a function of the object managed by the shared_ptr might just reset it, and now you've got a dangling pointer. If you pass by value, you ensure that the object will survive the current function call.
See here for much, much more information.
Example:
#include <memory>
#include <iostream>
std::shared_ptr<int> ptr(new int(42));
void foo(){
ptr.reset();
}
void bar(std::shared_ptr<int> const& p){
foo();
std::cout << *p;
}
int main(){
bar(ptr);
}
This should be taken with a pinch of salt. It can be adapted to prove that no type should ever be passed by const-reference - see for example http://ideone.com/1IYyC, which Benjamin Lindley pointed out in the comments.
However, more complex variations of this kind of issue do arise by accident in practice. This is the reason, for example, why we are warned that iterators (as well as const-reference return values) are invalidated by methods that mutate the referenced container. These rules are easy enough to follow in general, but occasionally more indirect and unexpected examples can catch people by surprise.
That being the case, it's best to avoid the extra layer of referencing when it's not needed.
If the function manipulating the object it should not be relevant if the object is contained in a smart pointer. It should accept a T&. Free functions that manipulate values are considered bad style by some. Taking the object by reference to const and returning a new value can be cleaner.
I've been passing by const& based on the idea that somewhere up the call stack someone should be holding a copy of the shared_ptr and therefore its life is guaranteed for my entire execution.
In the case that you are executing on a different thread, you need a copy - and for that matter anyone that stores the shared_ptr (not just uses it for the duration of their method) needs to store a copy, not a reference.
And, now that I've written that I'll go and read why people seem to be supporting the opposite mentality.
An example to what #Xeo said:
void foo(shared_ptr<bar> &p)
{
p.reset(); // ref_count == 0; memory is freed.
}
shared_ptr<bar> p(new bar); // ref_count == 1
foo(p);
This does not happen for const shared_ptr<bar> &p.