Recently I was writing some C++ code and one thing I needed to do is to pass a pointer of an object within itself to another function.
At first, I was using raw pointers and everything worked fine. However, later as I decided to use std::shared_ptr
instead, I encounter
error where the same pointer was freed twice. After some search online, I discovered that I need std::enable_shared_from_this
.
What’s causing the problem?
When I first learnt about std::shared_ptr
, I knew that it could keep track of how many references of the same pointer are there and
will only free the resources when the reference count goes to 0. I didn’t think too much about how this is actually implemented back then,
but obviously this is not some magic. The reason why it can keep track is because std::shared_ptr
has a pointer to a struct that stores the
reference count besides the pointer to the actual object. Therefore, when you create a std::shared_ptr
from another one, it will increment
the count properly (the two std::shared_ptr
s point to the same struct). If you create two std::shared_ptr
from the same raw pointer, although
they actually point to the same resource, they have no way of knowing it!
#include <memory>
#include <iostream>
using namespace std;
struct Obj
{
~Obj() {cout << "Destructor called on " << this << endl;}
};
int main()
{
Obj *ptr = new Obj();
shared_ptr<Obj> sptr_1(ptr);
shared_ptr<Obj> sptr_2(ptr);
}
$ g++ -std=c++11 -o double_free double_free.cc
$ MALLOC_CHECK_=1 ./double_free
Destructor called on 0x602010
Destructor called on 0x602010
*** glibc detected *** ./double_free: free(): invalid pointer: 0x0000000000602010 ***
When I was creating a std::shared_ptr
from this
inside an object, this is exactly the problem that I had in my code.
P.S. I must set
MALLOC_CHECK_
in order to force the check on double free. Otherwise the program finishes without error although the desctructor is clearly called twice on the same object.
std::enable_shared_from_this
to the rescue
When you are not creating a std::shared_ptr
of an object inside itself, you can obviously avoid the problem by creating std::shared_ptr
from existing
ones. How do we solve the problem if you must create inside the object? Turns out, there is something called std::enable_shared_from_this
in the standard
library. This is a class, which if you inherite your class from, allows you to create std::shared_ptr
from this
with the right reference counting.
#include <memory>
#include <iostream>
using namespace std;
struct Obj : public enable_shared_from_this<Obj>
{
shared_ptr<Obj> Get() {return shared_from_this();}
~Obj() {cout << "Destructor called on " << this << endl;}
};
int main()
{
shared_ptr<Obj> sptr_1 = make_shared<Obj>();
shared_ptr<Obj> sptr_2 = sptr_1->Get();
}
$ g++ -std=c++11 -o enable_shared_from_this enable_shared_from_this.cc
$ MALLOC_CHECK_=1 ./enable_shared_from_this
Destructor called on 0x604020
As demonstrated in the example above, the destructor is only called once although we are creating a std::shared_ptr
inside the object.
I haven’t dig into the source code of libc
myself to understand how this is actually implemented, but I saw this post on StackOverflow explaning how it could be done. Basically, std::enable_shared_from_this
can hold a std::weak_ptr
(this is an excellent use case of std::weak_ptr
in my opinion)
to the std::shared_ptr
when the std::shared_ptr
is constructed. std::enable_shared_from_this::shared_from_this()
can then create
new std::shared_ptr
s based on those information, thus having the correct reference count.
Conclusion
In this blog post, I explained why you would run into trouble by creating std::shared_ptr
from raw pointer in some cases. I also described how
to avoid this problem with the use of std::enable_shared_from_this
. I hope you have a better understanding of std::shared_ptr
after
reading this post!
References
- std::enable_shared_from_this - cppreference.com
- How std::enable_shared_from_this::shared_from_this works - StackOverflow