relay race

C++ Core Guidelines: Passing Smart Pointers

Passing smart pointers is a critical topic that is seldom addressed. This ends with the C++ core guidelines because they have six rules for passing std::shared_ptr and std::unique_ptr.

 

relay race

The six rules violate the import dry (don’t repeat yourself) principle for software development. Ultimately, we have only four rules that make our life as software developers much easier. Here are the rules.

 Let’s start with the first two rules for std::unique_ptr.

R.32: Take a unique_ptr<widget> parameter to express that a function assumes ownership of a widget

If a function should take ownership of a Widget, you should take the std::unique_ptr<Widget> by copy. The consequence is that the caller has to move the std::unique_ptr<Widget> to make the code run.

#include <memory>
#include <utility>

struct Widget{
    Widget(int){}
};

void sink(std::unique_ptr<Widget> uniqPtr){
    // do something with uniqPtr
}

int main(){
    auto uniqPtr = std::make_unique<Widget>(1998);
    
    sink(std::move(uniqPtr));      // (1)
    sink(uniqPtr);                 // (2) ERROR
}

 

Call (1) is fine, but call (2) breaks because you can not copy a std::unique_ptr. If your function only wants to use the Widget, it should take its parameter by the pointer or reference. The difference between a pointer and a reference is that a pointer can be a null pointer.

 

Rainer D 6 P2 500x500Modernes C++ Mentoring

  • "Fundamentals for C++ Professionals" (open)
  • "Design Patterns and Architectural Patterns with C++" (open)
  • "C++20: Get the Details" (open)
  • "Concurrency with Modern C++" (open)
  • "Generic Programming (Templates) with C++": October 2024
  • "Embedded Programming with Modern C++": October 2024
  • "Clean Code: Best Practices for Modern C++": March 2025
  • Do you want to stay informed: Subscribe.

     

    void useWidget(Widget* wid);
    void useWidget(Widget& wid);
    

     

    R.33: Take a unique_ptr<widget>& parameter to express that a function reseats the widget

    Sometimes a function wants to reseat a Widget. In this use case, you should pass the std::unique_ptr<Widget> by a non-const reference.

    #include <memory>
    #include <utility>
    
    struct Widget{
        Widget(int){}
    };
    
    void reseat(std::unique_ptr<Widget>& uniqPtr){
        uniqPtr.reset(new Widget(2003));   // (0)
        // do something with uniqPtr
    }
    
    int main(){
        auto uniqPtr = std::make_unique<Widget>(1998);
        
        reseat(std::move(uniqPtr));       // (1) ERROR
        reseat(uniqPtr);                  // (2) 
    }
    

     

    Now, the call (1) fails because you can not bind an rvalue to a non-const lvalue reference. This will not hold for the copy in (2). A lvalue can be bound to an lvalue reference. By the way. The call (0) will not only construct a new Widget(2003), but it will also destroy the old Widget(1998).

    The next three rules to std::shared_ptr are repetitions; therefore, I will make one out of them.

    R.34: Take a shared_ptr<widget> parameter to express that a function is part owner, R.35: Take a shared_ptr<widget>& parameter to express that a function might reseat the shared pointer,  and R.36: Take a const shared_ptr<widget>& parameter to express that it might retain a reference count to the object ???

    Here are the three function signatures we have to deal with.

    void share(std::shared_ptr<Widget> shaWid);
    void reseat(std::shard_ptr<Widget>& shadWid);
    void mayShare(const std::shared_ptr<Widget>& shaWid);
    

     

    Let’s look at each function signature in isolation. What does this mean from the function perspective?

    • void share(std::shared_ptr<Widget> shaWid): I’m for the lifetime of the function body a shared owner of the Widget. At the beginning of the function body, I will increase the reference counter; at the end, I will decrease the reference counter; therefore, the Widget will stay alive as long as I use it.
    • void reseat(std::shared_ptr<Widget>& shaWid): I’m not a shared Widget owner because I will not change the reference counter. I have not guaranteed that the Widget will stay alive during the execution of my function, but I can reseat the resource. A non-const lvalue reference is more like I borrow the resource and can reseat it. 
    • void mayShare(const std::shared_ptr<Widget>& shaWid): I only borrow the resource. Neither can I extend the resource’s lifetime nor can I reseat the resource. You should use a pointer (Widget*) or a reference (Widget&) as a parameter instead because there is no added value in using a std::shared_ptr.

    R.37: Do not pass a pointer or reference obtained from an aliased smart pointer

    Let me present you with a short code snippet to clarify the rule.

    void oldFunc(Widget* wid){
      // do something with wid
    }
    
    void shared(std::shared_ptr<Widget>& shaPtr){       // (2)
        
       oldFunc(*shaPtr);                                // (3)
       
       // do something with shaPtr
         
     }
    
    auto globShared = std::make_shared<Widget>(2011);   // (1)
    
    
    ...
    
    shared(globShared);                                 
    

     

    globShared (1) is a globally shared pointer. The function shared takes its argument per reference (2). Therefore, the reference counter of shaPtr will not be increased, and the function share will not extend the lifetime of Widget(2011). The issue begins with (3). oldFunc accepts a pointer to the Widget; therefore, oldFunc has no guarantee that the Widget will stay alive during its execution. oldFunc only borrows the Widget.

    The cure is quite simple. You have to ensure that the reference count of globShared will be increased before the call to the function oldFunc. This means you have to make a copy of the std::shared_ptr:

    • Pass the std::shared_ptr by copying to the function shared:
       void shared(std::shared_ptr<Widget> shaPtr){
         
         oldFunc(*shaPtr);
         
         // do something with shaPtr
           
       } 
      
    • Make a copy of the shaPtr in the function shared:
       void shared(std::shared_ptr<Widget>& shaPtr){
         
         auto keepAlive = shaPtr;   
         oldFunc(*shaPtr);
         
         // do something with keepAlive or shaPtr
           
       } 
      

    The same reasoning also applies to std::unique_ptr, but I have no simple cure in mind because you can not copy a std::unique_ptr. I suggest you should clone your std::unique_ptr and, therefore, make a new std::unique_ptr.

    What’s next?

    This was the last of my four posts about resource management in the C++ core guidelines. The C++ core guidelines have more than 50 rules for expressions and statements. I will have a closer look at my next post.

     

     

     

    Thanks a lot to my Patreon Supporters: Matt Braun, Roman Postanciuc, Tobias Zindl, G Prvulovic, Reinhold Dröge, Abernitzke, Frank Grimm, Sakib, Broeserl, António Pina, Sergey Agafyin, Андрей Бурмистров, Jake, GS, Lawton Shoemake, Jozo Leko, John Breland, Venkat Nandam, Jose Francisco, Douglas Tinkham, Kuchlong Kuchlong, Robert Blanch, Truels Wissneth, Mario Luoni, Friedrich Huber, lennonli, Pramod Tikare Muralidhara, Peter Ware, Daniel Hufschläger, Alessandro Pezzato, Bob Perry, Satish Vangipuram, Andi Ireland, Richard Ohnemus, Michael Dunsky, Leo Goodstadt, John Wiederhirn, Yacob Cohen-Arazi, Florian Tischler, Robin Furness, Michael Young, Holger Detering, Bernd Mühlhaus, Stephen Kelley, Kyle Dean, Tusar Palauri, Juan Dent, George Liao, Daniel Ceperley, Jon T Hess, Stephen Totten, Wolfgang Fütterer, Matthias Grün, Phillip Diekmann, Ben Atakora, Ann Shatoff, Rob North, Bhavith C Achar, Marco Parri Empoli, Philipp Lenk, Charles-Jianye Chen, Keith Jeffery, Matt Godbolt, and Honey Sukesan.

    Thanks, in particular, to Jon Hess, Lakshman, Christian Wittenhorst, Sherhy Pyton, Dendi Suhubdy, Sudhakar Belagurusamy, Richard Sargeant, Rusty Fleming, John Nebel, Mipko, Alicja Kaminska, Slavko Radman, and David Poole.

    My special thanks to Embarcadero
    My special thanks to PVS-Studio
    My special thanks to Tipi.build 
    My special thanks to Take Up Code
    My special thanks to SHAVEDYAKS

    Modernes C++ GmbH

    Modernes C++ Mentoring (English)

    Do you want to stay informed about my mentoring programs? Subscribe Here

    Rainer Grimm
    Yalovastraße 20
    72108 Rottenburg

    Mobil: +49 176 5506 5086
    Mail: schulung@ModernesCpp.de
    Mentoring: www.ModernesCpp.org

    Modernes C++ Mentoring,

     

     

    0 replies

    Leave a Reply

    Want to join the discussion?
    Feel free to contribute!

    Leave a Reply

    Your email address will not be published. Required fields are marked *