everyone. I find a problem in c++ deconstruction. I will show you my code below:
#include <iostream>
#include <memory>
#include <vector>
using namespace std;
class B;
class A{
public:
typedef shared_ptr<A> Ptr;
shared_ptr<B> b;
int index;
A(const shared_ptr<B>& b_, int index_):b(b_), index(index_){}
~A(){cout << "A is deleted." << endl;
cout << "A id is " << index << endl;}
};
class B{
public:
typedef shared_ptr<B> Ptr;
vector<A::Ptr> all_a;
void addA(){
for(int i=0; i<10; i++){
auto a = make_shared<A>(Ptr(this), i);
all_a.push_back(a);
}
}
int index;
B(int index_):index(index_){
}
~B(){
cout << "B is deleted." << endl;
cout << "B id is " << index << endl;
}
};
int main(){
B::Ptr b = make_shared<B>(5);
b->addA();
return 0;
}
A part of results is is
...
B is deleted.
B id is 5
A is deleted.
A id is 8
B is deleted.
B id is 5
A is deleted.
A id is 9
B is deleted.
B id is 5
double free or corruption (out)
[1] 29094 abort (core dumped) ./a.out
What is it happened with my code, how can i debug this problem. I thought the shared_ptr has some bug in this case.
There's one problem at play and another that'll pop up when you fix it.
shared_ptr
s that don't shareSo, the reason you see a lot of destructor calls for the same B
object and crash with a double-free error is this line:
auto a = make_shared<A>(Ptr(this), i);
In particular, Ptr(this)
makes a shared_ptr<B>
from this
that stands alone -- it cannot know that there are other shared_ptr
s somewhere else that feel responsible for the same object, it has its own reference count, and it'll believe that no one else is using the object it points to when that goes to zero. For all intents and purposes, it behaves like a unique_ptr
because it never shares its reference count with another shared_ptr
(except temporaries).
Unfortunately, you don't know a shared_ptr
that manages the object you're working on inside the member function. Fortunately, there's something in the standard library to help with that: std::enable_shared_from_this
It works like this:
class B : public std::enable_shared_from_this<B> {
// ...
auto a = make_shared<A>(shared_from_this(), i);
Note that if you do this, the code expects that B
will only ever be manged by shared_ptr
s. Although, really, it already does that, so that's okay.
Now, once you put that into your program, it'll not crash anymore. It'll also not print anything, as if nothing were ever deleted. What gives?
In fact, nothing is ever deleted, and the reason is that as far as all those shared_ptr
s are concerned, their objects never go out of use. We've stumbled upon the main reason that reference-counting does not a full garbage collector make.
Your B
object knows 10 A
objects, but I'll consider only one of them -- it's just the thing I'm about to show 10 times. They have shared_ptr
s to each other, and there are however many external shared_ptr
that keep them alive as long as they are needed. Once all those external references are gone, you expect them to be deleted, but consider what the situation at that point looks like:
+-----+ shared_ptr<A> +-----+
| | ----------------> | |
| b | | a |
| | <---------------- | |
+-----+ shared_ptr<B> +-----+
b
has a shared_ptr
to a
, so a
has a reference count of 1. a
has a shared_ptr
to b
, so b
has a reference count of 1. We're caught in a catch-22: a
is in use because b
is use, and b
is in use because a
is in use, so neither is ever deleted.
When working with shared_ptr
s, this kind of setup absolutely has to be avoided, and that's why weak_ptr
exists. I have to guess which way you want the ownership mechanics to go, but for example you could have non-owning references from the A
to the B
objects like so:
class A{
// ...
weak_ptr<B> b;
and in B
:
void addA(){
for(int i=0; i<10; i++){
auto a = make_shared<A>(weak_from_this(), i);
all_a.push_back(a);
}
}
Then the objects in all_a
will not keep the B
object that owns them alive. To use it, you would write
std::shared_ptr<B> b2 = b.lock();
to have a shared_ptr
that keeps it alive while you're working with it.
When you do shared_ptr<B>(this)
twice, you do not get 2 shared_ptr
which manage this
successfully. You get 2 shared_ptr
with separate control blocks each of which thinks they are the only manager of this
. Inevitably this will lead to a double delete or worse.
One way to solve this problem is to use shared_from_this()
instead of this
; see here for example.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With