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:
std::shared_ptr<void()>ee(fun);
not work?template <class Y, class Deleter> std::shared_ptr(Y* ptr, Deleter d);
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.
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.
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