GotW #105: Smart Pointers, Part 3 (Difficulty: 7/10)

JG Question

1. What are the performance and correctness implications of the following function declaration? Explain.

void f( shared_ptr<widget> );

 

Guru Question

2. A colleague is writing a function f that takes an existing object of type widget as a required input-only parameter, and trying to decide among the following basic ways to take the parameter (omitting const):

void f( widget& );
void f( unique_ptr<widget> );
void f( unique_ptr<widget>& );
void f( shared_ptr<widget> );
void f( shared_ptr<widget>& );

Under what circumstances is each appropriate? Explain your answer, including where const should or should not be added anywhere in the parameter type.

(There are other ways to pass the parameter, but we will consider only the ones shown above.)

28 thoughts on “GotW #105: Smart Pointers, Part 3 (Difficulty: 7/10)

  1. Q1: void f( shared_ptr );

    Heavy duty stuff going on here.

    Copy from widget*:
    – A new internal instance of SharedRef is allocated on heap. It is therefore better to use make_shared to pass in a shared_ptr instead because that avoids the separate allocation of SharedRef.

    Copy from shared_ptr:
    – Atomic increment of shared reference count. Probably the least costly operation.

    Copy from weak_ptr:
    – Atomic check for null and increment (CmpExchnge) of reference count in a loop. Much heavier.

    Q2:

    void f( widget& );

    – Non-polymorphic behavior is desired on widget inside f.
    – widgets lifetime is not affected inside f (like storing etc)
    – Works in all cases, a reference, raw pointer or smart pointers – all can be passed to it. like
    f( _widget );
    f( *pwidget );
    f( *uptr.get() );
    – Efficient because it does not have to deal with nullptr – good choice for private non-member interface of widget.

    void f( unique_ptr );

    – Restricts the use of shared_ptr/weak_ptr. Dangerous to pass a shared widget reference to it.
    – Must not be used except when it is desired to ** assert ** exclusive ownership of widget.

    void f( unique_ptr& );

    – Appropriate when you have unique_ptr members/globals. It is therefore an efficient choice as private interface.
    – If you add const then it can also take in a raw pointer, which adds flexibility at the cost of safety.

    void f( shared_ptr );

    – Better than unique_ptr variants because it allows for greater flexibility, but more expensive in terms of cpu.
    – Makes the widget shared forever. The general problem with shared_ptr is that they make objects shared forever and there is no simple and safe way to make then unique again.

    void f( shared_ptr& );

    – Appropriate when you have shared/weak_ptr members/globals.
    – Makes the widget shared forever.
    – If you add const then it can also take in a raw pointer, which adds flexibility at the cost of safety.

  2. JG:
    Correctness seems fine. The function is saying that it is taking (and could retain) shared ownership of the object, and that it may modify said object. As long as this is the intent it seems fine to me.

    Performance is probably fairly cheap. Assuming the caller is passing in a shared_ptr as well, the pain of allocating the bookkeeping struct shared_prt uses should have already been done (maybe with make_shared even), and the new one will just point to that. I suppose its possible for the caller to pass a raw pointer, and then you would take the pain since the parameter would be the first shared_ptr in that case. Probably bad style to do that though (is is possible to prevent at compile time?)

    Guru:

    void f( widget& ); -> void f( const widget& );
    Use if not taking any ownership. i.e. your not going to be passing it off to a task or something. Your just going to use it and be done with it by the time the function returns.

    void f( unique_ptr ); -> void f( unique_ptr );
    The function is taking ownership and will not return it back to the caller. Kind of strange to take full ownership to an object you can’t modify (or was when I first looked at it). One example use I can think of, maybe the widget is being released, but you wanted to async save it out to disk or something. f could take the widget pass it to a task to save it, and it get freed after that automatically. i.e. the caller is done with it, but you want to fire and forget one last thing to do with it before it goes away.

    void f( unique_ptr& ); ->void f( const unique_ptr& );
    seems like the same use case as void f( const widget& ); I would prefer the simple reference. function is not taking any ownership, will not retain any ownership, and will not use the object after it returns in any way.

    void f( shared_ptr ); -> void f( shared_ptr );
    f is taking shared ownership, and may retain it, but is promising to not modify the object. the save example works here too, but in this case the caller is keeping shared ownership as well. Maybe the parent window is still up, and its just wanting to save the state. in case the window closes later, but before the save task finishes, no problem f has partial ownership.

    void f( shared_ptr& ); -> void f( const shared_ptr& );
    seems the same as void f( const unique_ptr& ); and void f( const widget& ); I still prefer the regular reference. no need for f to know how lifetime is being managed on the object.

  3. JG Question:

    void f( shared_ptr );
    There are performance issues due to ref counting but there are no correctness issues.
    Unlike what others have said, thread safety is not an issue here. At least not more than it is using any other parameter type. If you share a resource using multiple threads you must synchronize it regardless of whether you use shared_ptr, shared_ptr& or raw pointers/references.
    Performance wise using shared_ptr& is always better than passing it by values and it should be preferred over it by default.

    Guru Question:

    I will only address declarations I find useful.

    void f( const widget& );
    – Nothing new about that but it is still the preferred way for most use cases.
    – For an input parameter omitting const should of course be done if widget needs to be changed (e.g. item 23 @Effective C++).

    void f( const shared_ptr& );
    – Pass by reference for performance.
    – Mark shared_ptr as const to disallow reset which may break caller code.
    – Don’t mark widget as const because a shared_ptr of widget and shared_ptr of const widget are not the same and even though it may actually be the same widget it has a different ref count.
    – This is preferred over raw references when widgets are already managed by shared_ptr and f may optionally want to share ownership of the widget (e.g. widgets are part of a cache).

    void f( unique_ptr );
    – Do not mark unique_ptr as const as it will force the widget to be destroyed when f returns (right?). If this is the desired behavior it seems cleaner to do it inside f’s implementation as the caller doesn’t need to know about that.
    – I rarely see a reason to manage a const object using unique_ptr so I’m not marking widget as const.
    – Typical usage would be passing ownership to another object. In example when f takes a factory generated object. For methods a good example is Strategy pattern’s set strategy method.

  4. Is this a sneaky way to ask 14 Guru Questions in one? :-) I’ll note my assumptions: I’m taking your term “function declaration” quite strictly, not considering methods or templates. Private methods in particular can have internal conventions that don’t need to be expressed in the signatures. I’m also assuming that the calling code is not keeping any raw pointers or references to the widget or any of its members (i.e. it only uses managed pointers). Also, I’ll deal with the signatures in pairs, noting that passing any kind of pointer to a non-const widget is not really “input-only” except when f takes sole ownership of the widget.

    void f(widget const&);
    void f(widget&);
    

    f wishes to read from its argument; it may or may not retain any references, but without further documentation we don’t know. The widget will endure until at least the end of the function call.

    void f(unique_ptr<widget const>);
    void f(unique_ptr<widget>);
    

    f recieves ownership of the widget, and the calling function will likely pass in a temporary or use std::move to indicate that it’s relinquishing ownership. Unlike the other pairs, these two are equivalent from the caller’s point of view, as the caller will never see the widget again, and so doesn’t know whether it has been modified.

    void f(unique_ptr<widget const> const&);
    void f(unique_ptr<widget> const&);
    

    f is “borrowing” the widget without taking ownership. Similar to void f(widget&), but it is clearer that f must not retain any references, and it may be more convenient for the caller, who has a unique_ptr (it’s certainly more reassuring to the caller). Unless you know the caller will already have a unique_ptr (perhaps because that’s the only return type from a factory, for example), I’d recommend this only as an overload for the plain reference versions f(widget const&) and f(widget&).

    void f(unique_ptr<widget const>&);
    void f(unique_ptr<widget>&);
    

    f has access to the widget, and may or may not take ownership. Possibly a performance hack when the unique_ptr is to be handed to another function, but probably only useful if it’s a run-time decision whether or not ownership is transferred (I’m hoping that compilers are smart enough to optimise out copying when it can be determined at compile-time; correct me if they are not allowed to). If f does not pass ownership on, and does not assign to the unique_ptr, it has access for the duration of the call.

    void f(shared_ptr<widget const>);
    void f(shared_ptr<widget>);
    

    f has access to the widget, and receives shared ownership. It may hold (copies of) the shared_ptr as long as it likes – longer than the calling code, if it wishes – and pass it to other code at will.

    void f(shared_ptr<widget const> const&);
    void f(shared_ptr<widget> const&);
    

    f may borrow the widget or take shared ownership (because shared_ptr‘s internal reference count is not public state, its copy constructor will accept a reference to const). The widget remains available to the calling code after f returns.

    void f(shared_ptr<widget const>&);
    void f(shared_ptr<widget>&);
    

    f may simply borrow the widget, take shared ownership, or deprive the caller of the widget (via this pointer). It’s hard to see a use case for this signature that’s not served by the previous one (remembering that other shared pointers may exist).

    When I say above that f “must not retain any references”, I include pointers and references to the widget or any of its constituents – anything that may be removed when the widget is destroyed.

  5. The JG question seems to be pretty well addressed, but I think people saying that certain signatures are bad is wrong. I’m by no means a guru, but maybe I can promote

    void f( widget& );

    Simple- take a reference to the object. If it’s const, it means that we are just optimizing away a copy that is unneeded because we do not touch it. If it’s non-const, it means that we are performing some sort of operation on the widget and need these changes propagated back to the caller.

    void f( unique_ptr );

    Used if we want to take ownership of the object. For example, if f creates a new thread and it needs to be passed a widget which it takes ownership sole ownership of, one could use this signature along with std::move. Or if f isn’t just a void function but constructs a wrapper object for widget, then it could be used to say that it needs an object constructed for it.

    void f( unique_ptr& );

    This one is interesting. It is functionally equivalent to just passing a regular old pointer. However, if one uses const in this case it is actually *safer* than void f(widget&), because if one passes a unique_ptr by const reference they are allowed to access the object, but may not store that object anywhere. With the first prototype, f could store the address of the widget somewhere, and if the widget is stack allocated or somebody deletes it, game over. However, a const reference guarantees that the unique_ptr will ONLY be accessed during the actual execution of f (barring const casts).

    void f( shared_ptr );

    This is useful if we need to have shared ownership of the object. Const doesn’t really make sense if we part ‘own’ the object though. I don’t think it needs much explanation.

    void f( shared_ptr& );

    Taking the shared_ptr by reference allows us to avoid the somewhat costly reference count increment. This is useful if we want to pass it somewhere else, and we’re just a mediator that doesn’t need to have ownership. Const could also make sense if we know that the caller will have a shared_ptr object and don’t want to require them to do anything with the object to pass it in, but is otherwise equivalent to the bare reference AFAIK. It is dangerous, though, in that we can create copies of the reference and undermine the whole point of the shared ptr. But as long as it’s internal to the application and not part of a callback signature, as long as you’re careful, you should worry more about Murphy and less about Machiavelli.

    In short, they all have their uses.

  6. 1. Implications of: `void f( shared_ptr<widget&rt; );`

    Performance:

    The reference count for the shared widget will be incremented upon calling f and decremented upon returning from f. This probably cannot be optimized away because, in a multithreaded application, it may be necessary to ensure the widget is alive for the duration of the call. The increment and decrement may be lock-free, but they are likely done with atomic operations that are more costly than a plain increment and decrement. So there is a small performance cost that may not be absolutely necessary. Nevertheless, it seems a very small tradeoff for the correctness guarantees, except, perhaps, in the most performance-critical sections of the code.

    Correctness:

    * The widget will be guaranteed to live throughout the duration of the call to f.

    * The widget may be modified (or even replaced) by f, which would be visible to all other owners. If other owners may be running in other threads, you’re going to need a locking mechanism.

    * If f is a method of object foo, then foo may retain a reference to the widget by storing another shared_ptr to it as a member.

    * The function f cannot be called with a raw pointer, which means it can work only with dynamically-allocated widgets. You cannot call `f(&w1)` where `w1` is a member, stack, or static variable.

    2. Options for passing a required, input-only widget:

    `void f( widget& );`

    This seems the most appropriate if we add `const` to make the input-onlyness explicit, as in `void f( const widget & );` (This is equivalent to `void f( widget const & );`.) This makes it possible to pass any widget, whether or not the widget is dynamically allocated.

    `void f( unique_ptr<widget&rt; );`

    Since unique_ptrs cannot be copied, this seems rather useless.

    `void f( unique_ptr<widget&rt;& )`

    This seems less useful than passing the widget by reference. Here you have double indirection (one level of which may be optimized away), and the function can only be passed widgets that are already managed by only a unique_ptr. There is no useful way to add const to indicate the read-only aspect of the parameter. If you try `unique_ptr<const widget&rt;&`, then you won’t be able to pass in a `unique_ptr<widget&rt;&`. And if you try `const unique_ptr<widget&rt;&`, f would still be able to modify the widget.

    `void f( shared_ptr<widget&rt; )`

    This increments and drops the reference count, so there’s a performance impact as in question 1. It’s useful only with dynamically-created objects. There’s no good place to put `const`.

    `void f( shared_ptr<widget&rt;& )`

    This is bad because you’ve effectively created another reference without actually changing the reference count. In a multithreaded app, you’ve lost the certainty that the widget will be alive for the duration of the call.

  7. addition to unique_ptr: of course the pointer could be stored somewhere, thus not destroying the widget at all. Nevertheless unique_ptr means “don’t bother, its mine now” and the callee need not guarantee that the widget remains unchanged -> no const here.

  8. I had a long response typed in yesterday – an because of a missing password were not able/allowed to post it. So here’s the short answer to Q2:

    – f( widget& ) should be f( const widget& ), because as the widget is an input-only parameter the callee should not change it. Ownership (and the choice of how to manage it) remains at the caller.
    – f( shared_ptr ) means the shared_ptr or a weak_ptr is to be stored somwhere as Alf pointed out.. As it is shared ownership the caller might keep a reference to the widget as well and won’t expect changes (input only again), so it should be shared_ptr
    – f( unique_ptr ) means taking over complete ownership, saying “give me that widget, what I am doing with it is none of your business”. Taking over ownership means you want to do more than just read out some values (e.g. dismantle it and use some of the widget’s resources or whatever), so don’t pass it as unique_ptr
    – passing smartpointers (with ownership semantics) by reference makes no sense. Either f needs the ownership (shared or unique), then pass the smart pointer by value, so it gets the ownership. Or f does not need ownership, then leave it to the caller how the ownership is to be managed and let him pass a plain reference instead.

    in short:
    f( widget const& ): “Just let me see”
    f( shared_ptr ): “I’ll be watching you”
    f( unique_ptr ): “Give. I’ll consume it.”

  9. Most people seem to have overlooked the proper time to use 2c… when the function may swap the object with a different one, therefore needing to use unique_ptr::operator=

    Of course, we are told in this circumstance that the parameter is input-only, which rules out that usage.

  10. JG Question
    1. What are the performance and correctness implications of the following function declaration? Explain.

    -void f( shared_ptr );

    Performance impact on instantiating shared_ptr, it’s copy dtor and, of course ref. counting (entering function and on stack unwinding, ref-count decrease on destructor.

    Guru Question
    2. A colleague is writing a function f that takes an existing object of type widget as a required input-only parameter, and trying to decide among the following basic ways to take the parameter (omitting const):

    Under what circumstances is each appropriate? Explain your answer, including where const should or should not be added anywhere in the parameter type.

    -void f( widget& );
    This is correct when function intends to full access to the widget widget (content may change).

    -void f( unique_ptr );
    Didn’t try this, but since unique_ptr is not copiable (copy ctor = delete), function call shall be done using std::move (not sure how vc++ handles it). This is correct when the pointed ownership is intended to be handled to the function – i.e. FinalizeAndArchiveData(std::move(msg)); widget will be destroyed when leaving function.

    -void f( unique_ptr& );
    It’s correct when accessing a pointed object owned exclusively by other (but not taking ownership).

    -void f( shared_ptr );
    I don’t see any correct case of using this one in a transparent way. Refer to Q1 for details.

    -void f( shared_ptr& );
    Correct when new ptr owners (references) that will persist after the function exists, are created inside the function.

  11. 1. The function signature implies that something will be taking shared ownership of the object owned by the shared_ptr. Calling the function will increment the reference count of the shared_ptr unless it is called with an r-value reference (i.e., temporary), in which case no reference count increment occurs. For this reason, shared_ptr parameters should always be declared as presented here, not as a reference to const. In the case where the function is called with an r-value reference, no increment occurs; otherwise, exactly one increment occurs, just as if the parameter had been declared with a reference to const. The difference lies in when that increment occurs: In this case, it happens when the parameter is constructed; the destination shared_ptr is then move-initialized from the parameter instead of copy-initialized. With a ref-to-const parameter, the increment occurs when the destination shared_ptr is always copy-initialized, which may result in a useless increment/decrement pair.

    2. Given that the widget is an input-only parameter (which I take to mean unmodified and used only for the duration of this function), the only correct signature is

    void f(const widget&);
  12. A little error:
    void f( shared_ptr );
    I would only use this if the the lifetime w/o shared ref may be shorter than the time to execute f, to ensure that the ptr to that object is valid all the time (i problably changed the parameter to shared_ptr).

    Should be:
    void f( shared_ptr );
    I would only use this if the the lifetime w/o shared ref may be shorter than the time to execute f, to ensure that the ptr to that object is valid all the time (i problably changed the parameter to const widged).

  13. 1) void f( shared_ptr );
    – Creates a copy of shared_ptr if the function is called.
    -> Increment of the (strong-)refcounter in a thread safe way, witch trigger some sync mechanics, they will decrease performance, in comparison of a simple ptr copy.
    – This ensures that the function has (shared-)ownership of the object and ensures that the lifetime of that object is at least as long as the function is executed (asuming that the pointer is not null).
    – f may modify the state of the referenced object, because the wraped pointer is nonconst.

    2)
    void f( widget& );
    I would change the parameter to const, and use it every time the object outlives the execution of f.

    void f( unique_ptr );
    Is this even posible? unique_ptr is noncopyable so it should refuse to compile.

    void f( unique_ptr& );
    I would instead unwrap the object and pass it as reference (first f).

    void f( shared_ptr );
    I would only use this if the the lifetime w/o shared ref may be shorter than the time to execute f, to ensure that the ptr to that object is valid all the time (i problably changed the parameter to shared_ptr).

    void f( shared_ptr& );
    I would instead unwrap the object and pass it as reference (first f).

  14. Hm, you ask, “what are the performance and correctness implications of the following function declaration”,

    void f( shared_ptr<widget> );
    

    Well, there is only one good reason for passing a shared_ptr as in-argument, and that’s when the function may store the pointer somewhere.

    So this is the main performance implication, that the referred to object may be kept alive via the stored pointer.

    At a more microscopic level, passing the shared_ptr by value incurs some reference counting. It’s fast but still it’s needless overhead. Better pass that smart pointer by reference to const.

    Regarding correctness, a possible problem is indicated by the unqualified shared_ptr. If this function declaration is in the global namespace in a header, then uh oh.

    Another problem is that shared_ptr allows nullpointers. So the function declaration is not by itself enough to decide in general whether a call is correct. It needs to be augmented with documentation or implementation code or proper typing that enforces requirements, e.g. like this:

    #if defined( _MSC_VER )
    #   pragma warning( disable: 4345 ) // Warning: T() will default-initialize!
    #endif
    
    #include <assert.h>     // assert
    #include <memory>       // std::shared_ptr, std::make_shared
    #include <utility>      // std::forward
    
    template< class TpPointee >
    class SharedObject
        : public std::shared_ptr< TpPointee >
    {
    public:
        typedef TpPointee                   Pointee;
        typedef std::shared_ptr< Pointee >  Base;
        
        using Base::get;
    
        SharedObject( Base const& nonNullPtr )
            : Base( nonNullPtr )
        {
            assert( get() != 0 );
        }
    
    #if !defined( _MSC_VER )    
        template< class... Args >
        explicit SharedObject( Args&&... args )
            : Base( std::forward<Args>( args )... )
        {
            assert( get() != 0 );
        }
    #endif
    };
    
    class Widget {};
    
    void foo( SharedObject<Widget> const& ) {}
    
    int main()
    {
        SharedObject<Widget> const o = std::make_shared<Widget>();
    
        foo( o );                                   // Efficient
        foo( std::make_shared<Widget>() );          // Correct
    #if !defined( _MSC_VER )
        foo( SharedObject<Widget>( new Widget() ) );// General
    #endif
    }
    

    You also ask about choosing between a number of different function signatures:

    void f( widget& );
    void f( unique_ptr<widget> );
    void f( unique_ptr<widget>& );
    void f( shared_ptr<widget> );
    void f( shared_ptr<widget>& );
    

    Supposedly this is a “guru question”, but if it is, then I have failed already.

    I don’t grok the purpose here.

    The three signatures of the form f( T& ) lets the function modify the actual argument. The actual argument cannot be an rvalue. This is a novice question.

    The f( unique_ptr<widget> ) signature passes ownership into the function. Again, this is a novice question.

    The f( shared_ptr<widget> ) signature shares ownership with wherever the function decides to copy the pointer. This may possibly be a question for more experienced programmers? It involves a subtle convention, that of indicating what the code does by not requiring arbitrary things.

  15. Now my last reply seems silly. Let’s try with SGML entities: last parts read: it would have some type like unique_ptr<HANDLE, void(*)(HANDLE)>, which is not compatible with unique_ptr<T>, aka, unique_ptr<T, default_delete>. Another example would be a unique_ptr<FILE, void(*)(FILE*)> that handles a C library FILE pointer.

  16. I forgot one thing: unique_ptr doesn’t type-erase the deleter. This means that any function taking a unique_ptr is effectively limiting how the caller allocates the object. The examples will accept unique_ptr that allocate with new, but not, for example, a unique_ptr that is holding some other resource, like for example, a Windows handle: it would have type `unique_ptr`, which is not compatible with `unique_ptr`, aka, `unique_ptr<T, default_delete>`.

  17. Ok, my signatures got messed up. I will try again (hoping that it works) …

    f( widget const& )
    // f( unique_ptr<widget> )
    f( const unique_ptr<const widget>& )
    f( shared_ptr<const widget> )
    f( const shared_ptr<const widget>& )
    
  18. I am taking a shortcut and extend stackedcrookedCrooked’s comments.

    1a. As Martinho already mentioned, it is not only reference counting but also the thread-safety guarantees which will result in a performance penalty. Regarding the correctness, the function is allowed to modify the widget which may be unintended.

    2.1. For read only cases, I would change the signature to “widget const&”.

    2.2. Right now, I cannot think of a good use case for this signature but probably I need to try harder. I don’t see when I would want to give away ownership to a free function.

    2.3. For read only cases, I would change the signature to “const unqiue_ptr&” where the “const widget” is most important.

    2.4. For read only cases, I would change the signature to “shared_ptr”. Due to the performance penalties of 1. this signature should be scarcely used. The advantage is it allows to implicitly convert a non const shared_ptr to a const one without using const_pointer_cast.

    2.5. For read only cases, I would change the signature to “const shared_ptr&”. Objects of type shared_ptr can only be passed through const_pointer_cast. The signature is preferred over 2.4 since for objects of type shared_ptr, no performance penalty occurs.

    Pointer versions (besides 2.2) make sense when the actual object is allowed to be a nullptr. Version 2.1 is not possible in that case. In case it is guaranteed that the object is never a nullptr, version 2.1 might be preferred.

  19. Note – not to be one of those guys who changes their answer… ok, I’d like to change my answer – taking a const reference to widget is clearly a great way to implement f given the implementor only wants read only access.

  20. 1:

    In terms of performance, passing a shared_ptr by value means potentially performing a copy of that shared_ptr. That means, at the very least, an atomic increment. Depending on how it is implemented, this could be a full-on mutex lock.

    In terms of correctness, by taking the shared_ptr by value, it makes a strong statement that this function (and any function that it calls) now *owns* the pointer. Shared ownership of course. This is correct only if `f` or something it calls *needs* to own the pointer. Otherwise it’s not a good idea.

    2:

    a) Using a `const&`, what this says is that this function needs the parameter (hence it isn’t a pointer which could be NULL), that it will not modify the parameter (hence the `const`), and that the function will not claim any form of ownership over that parameter. Taking it by non-`const` is a terrible idea for an input-only parameter. It is tantamount to lying to the user; a non-`const` reference parameter is a sign to any C++ programmer that the state of the object can, and will, be modified. If this is indeed an input-*only* parameter, it is a very bad idea to take it by non-`const` reference.

    b) This is appropriate if the function is claiming total ownership of the object. And if the user can reasonably be expected to have unique ownership of that resource. There is no reason to make the value object itself `const`, as this would inhibit claiming ownership. However, the value being pointed to could itself be `const`, if that is warranted.

    c) This is stupid. Basically, what it says is that the function *may* claim ownership of the pointer. Don’t be so wishy-washy; either you’re claiming ownership or you’re not. Don’t make the caller have to check the pointer afterwards to see if you actually own it.

    Taking the unique_ptr by `const&` makes even less sense. If the function isn’t allowed to claim ownership (which is what `const&` will do, since you can’t move from it), just pass the *object itself* by `const&`. There’s no point in putting a smart pointer in the signature if ownership isn’t being dealt with.

    d) This is appropriate if the function is claiming shared ownership of the object. And if the user can reasonably be expected to have shared ownership of that resource. As with the `unique_ptr` case, there is no sense in making the `shared_ptr` itself `const`. Making what it points to `const` may or may not be appropriate, depending on what one needs to do.

    e) This makes even less sense than the `unique_ptr` version. Since ownership is shared, if the function *might* take ownership, it may as well just copy the object. Plus it’s more dangerous, since the function could actually *move* from it, thus leaving the caller with an empty `shared_ptr`. This is… alarming.

    The `const&` case here actually has a purpose. It’s for cases where there is a pass-through of ownership, perhaps among many objects. This function may itself not claim ownership, but someone 3 functions down the call-stack will. You don’t want to have 3 copies of the same object all on the stack, thus performing 6 pointless atomic increments/decrement-exchanges. So you can go by `const&`.

  21. I’m by no means a guru – and am probably flat out wrong about some of the stuff below – but here’s my answer.

    1) Calling f will create a new shared_ptr object, the copy constructor of which will increment the internal reference counter of the passed in shared_ptr. With regards to performance, In many/most cases this will be cheaper than constructing a new widget value by copy constructor, but is certainly less performant than passing widget by reference or address. Also, unlike passing widget by value, f ( shared_ptr ) still allows modification of the widget object itself. Because it is non const, it would be possible to reassign the shared_ptr to another shared_ptr object, which would simply decrement the original argument’s internal reference counter during assignment.

    2)
    void f( widget &) –
    This provides better performance than passing widget by value. Widget argument may be modified. Lack of const not only violates semantic implications of ‘const’-ness that implementor of “f” suggests, but also makes this function not able to bind to rvalues. This implementation would therefor be inferior to void f (const widget &).

    void f( unique_ptr ) –
    Interesting because unique_ptr is non copyable. Would potentially be able to pass in a raw pointer (??) or use std::move to use the move constructure via C++ 11 rvalue references. In the case of move, this would mean the passed in unique_ptr is invalidated by calling the function f which would certainly have consequences, unless f typically takes in rvalue parameters (returned unique_ptr objects of other functions, perhaps). I think any implementation like this is probably a design a mistake.

    void f(unique_ptr &) –
    This bypasses the issue with the copy constructor by using a reference. This will allow access to the widget object for both read/write. ‘const’ has no effect on the ability of the argument to modify the underlying widget, but would allow the compiler to bind this to rvalues of unique ptrs. To avoid changes to widget, the type would need to be f(unique_ptr &). Doing this will only bind to other unique_ptr objects, however, not to unique_ptr.

    void f(shared_ptr) –
    See #1 above. This is widely appropriate for passing shared_ptr arguments to functions. Additionally, you can declare both the shared_ptr and the widget type as const for additional type safety without impacting the caller. That is, calling f ( const shared_ptr ) will work with a shared_ptr and provide the semantic const-ness that f guarantees. It will increment/decrement the internal reference count upon construction when function is called and destruction when function returns.

    void f( shared_ptr &) –
    A reference to the shared_ptr bypasses object construction and does not increment the internal reference counter when called. Without const shared_ptr cannot bind to rvalues passed to f. Because it is a reference, we cannot add const to the shared_ptr type and bind to non-const callers, limiting it’s use in that scenario.

    My winner for the implementor of f would be “void f(const shared_ptr)”.

    Looking forward to all the corrections coming my way :)

  22. Note: Concerning the second queston: Passing the shared_ptr (const ref) can be useful if the function will create a new thread that shares ownership of the passed variable. The thread however must get a *copy* of the shared_ptr.

  23. 1a. Passing shared_ptr by value increments the refcount when entering the function and decrements it when exiting the function. This may produce unwanted overhead. Passing by const reference avoids this.

    1b. The function signature basically says: “Please share the ownership of this object with me.” For functions it seems unlikely to be intented.

    2.1. The const reference version is *the* correct signature in most situations. (Non-const reference can be used for output parameters.)
    2.2. This one can be used if you want to give away ownership. To call it you must use the std::move function. E.g: foo(std::move(ptr));
    2.3. Should probably never be used IMO. (Const or nonconst.)
    2.4. Should probably never be used IMO. (Const or nonconst.)
    2.5. Unlikely IMO. But const version can be used if the function wants to check if the pointer is initialized. The non-const overload can also reset the pointer if desired.

  24. I recently posted an answer on Stack Overflow that covers most of the shared_ptr related parts: http://stackoverflow.com/questions/10826541/passing-smart-pointers-as-arguments/10826907#10826907

    What it doesn’t address is question #1 and the unique_ptr parts of question #2.

    #1 Passing a shared_ptr by value can result in a copy (when the argument is an lvalue). That probably means some synchronization will happen to maintain the shared_ptr thread-safety guarantees while updating a reference count. Ideally this will only involve lockfree operations.

    #2 If you want to operate on a widget without messing with its ownership semantics, take a reference. Make it to const if it’s a non-mutating operation.

    I think the rules for taking unique_ptr parameters are similar to the ones I outlined for shared_ptr on my Stack Overflow answer: if you want to take exclusive ownership of an object, take a unique_ptr by value. If you want to operate on a smart pointer (instead of the object it points to), take it by reference. Make it const if the operation is not mutating, like checking if the pointer is empty.

Comments are closed.