Search code examples
c++shared-ptr

What does this shared_ptr snippet say?


void del(void(*)()) {}
 
void fun() {}
 
int main()
{
    std::shared_ptr<void()> ee(fun, del);
    (*ee)();
}

This is a piece of code found on cppreference.com, I am trying to understand shared_ptr uses and this is an example I came across. I tried running this and making some modifications to this like adding cout statements inside the function body but I am unable to understand what is exactly happening here.

Adding a bit more of context related to my doubt,

My doubts specifically include:

  1. This shared_ptr ee gets initialised with a function so why does simply std::shared_ptr<void()>ee(fun); not work?
  2. Why do we have 2 functions in the constructor of ee ( I am assuming shared_ptr implementation is a class)?
  3. Why does del have to take in a function pointer as its argument? What is the significance of that here?

Solution

  • template <class Y, class Deleter>
    std::shared_ptr(Y* ptr, Deleter d);
    
    1. This shared_ptr ee gets initialised with a function so why does simply std::shared_ptr<void()>ee(fun); not work?

    The point is to create a context in which del is called after fun no matter what else is happening in the function in which ee is declared. It protects against early returns and exceptions.

    1. Why do we have 2 functions in the constructor of ee ( I am assuming shared_ptr implementation is a class)?

    fun is there mearly to make the shared pointer think it is owning a resource so that it will call the Deleter function when ee goes out of scope.

    1. Why does del have to take in a function pointer as its argument? What is the significance of that here?

    That's because the shared_ptr will call the Deleter function with a pointer to the resource it's owning. Since it's a function pointer, it can't really delete it - but it's needed to fulfill the contract you make when creating a shared pointer to a function.

    The dereferencing of ee and calling the result is for clarity. It could just as well have been fun();, but (*ee)(); is supposed to make readers of the code see the tight connection between the fun call and the necessity to call del when the function returns. It's not a "true" context handler because it opens up for mistakes:

    {
        std::shared_ptr<void()> ee(fun, del);
        // here someone mistakenly adds an early return:
        if (argc == 123) return 1;
        (*ee)();  //  or  fun();
    } // del may now be called even if `fun` was never called
    

    A similar construct without using shared_ptr could look like this:

    void del() {}
    void fun() {}
    
    template <class Out>
    struct call_context {
        template <class In, class O, class... Args>
        call_context(In&& in, O&& o) : f(std::forward<O>(o)) {
            std::invoke(std::forward<In>(in));
        }
        ~call_context() { f(); }
        Out f;
    };
    template <class In, class Out>
    call_context(In&&, Out&&) -> call_context<Out>;
     
    int main()
    {
        // call fun and will call del later no matter how `main` is exited:
        call_context cc(fun, del);
    }
    

    Another example:

    #include <iostream>
    
    // same call_context definition as above
    
    void fun() {
        std::cout << "Fun called\n";
    }
    
    int main() {
        call_context cc(fun, [] { std::cout << "main exited\n"; });
        std::cout << "---\n";
    }
    

    Output:

    Fun called
    ---
    main exited