What Happens to a weak_ptr when Its shared_ptr is Destroyed? - c++

It seems that a weak_ptr somehow just knows when the shared_ptr it references has been destroyed. How is that? Is there a constant link maintained or something?
Take the following code for example:
weak_ptr<int> test() {
shared_ptr<int> foo{new int};
return foo;
}
int main() {
auto foo = test();
cout << foo.expired() << endl;
}
I would have expected a segfault when the weak_ptr<int> goes to check on the state of the shared_ptr<int> but there isn't one. The weak_ptr<int> correctly identifies the memory as deallocated. How does it know?

A std::shared_ptr is created using two pieces of memory:
A resource block: This holds the pointer to the actual underlying data, e.g. 'int*'
A control block: This holds information specific to a shared_ptr, for example reference counts.
(Sometimes these are allocated in a single chunk of memory for efficiency, see std::make_shared)
The control block also stores reference counts for weak_ptr. It will not be deallocated until the last weak_ptr goes out of scope (the weak pointer reference count drops to zero).
So a weak_ptr will know that it's expired because it has access to this control block, and it can check to see what the reference count is for a shared_ptr

Related

Smart pointers to an object explicitly created object

I have read a lot of issues created in regard to this but was not able to answer my question.
I have created a class as follows -
class exampleClass{
public:
exampleClass(int n){
cout<<"Created Class"<<endl;
this->number = n;
}
~exampleClass(){
cout<<endl<<"This class is destroyed Now"<<endl;
}
template<typename t> t
addNum(t a, t b){
return a + b;
}
void print(){
cout<<this->number<<endl;
}
private:
int number;
};
and I make 2 shared_ptr(or for that matter unique_ptr, error is same) as follows -
int main(){
exampleClass* object = new exampleClass(60);
std::shared_ptr<exampleClass> p1(object);
std::shared_ptr<exampleClass> p2 (object);
p1->print();
}
Now the error it throws at the end is -
free(): double free detected in tcache 2
Aborted (core dumped)
I am not able to understand why the error at the end. Shouldn't the above code be equal to p2 =p1(in case of shared_ptr) or p2 = std::move(p1) for unique_ptr as both the pointers are for the same object?
TIA
PS - The title might be a little misleading or not accurate,but I did not know what exactly should be a title.
When you create a smart pointer, it will take ownership of the pointer, and deletes it when it goes out of scope (or when the last reference is done in case of a shared pointer).
When you create two smart pointers from the same raw pointer they both will delete the pointer at the end of their life, because they don't know about each other.
int main()
{
// Create a shared pointer with a new object
std::shared_ptr<exampleClass> p1 = std::make_shared<exampleClass>(60);
// Now you can safely create a second pointer from your existing one.
std::shared_ptr<exampleClass> p2 = p1;
p1->print();
p2->print();
}
When you create a shared_ptr from a raw pointer, it takes ownership of the raw pointer, and when the smart pointer goes out of scope, it will call delete on the owned resource. Giving the same raw pointer to 2 different shared_ptrs causes a double free, as both of them will try to free the resource.
If you need 2 shared_ptrs that share the same resource, you can copy the first one:
int main(){
exampleClass* object = new exampleClass(60);
std::shared_ptr<exampleClass> p1(object);
std::shared_ptr<exampleClass> p2 = p1;
}
This way they share ownership of the resource (sharaed_ptrs have an internal reference counter that tracks how many of them own a resource. When you copy a shared_ptr, the reference counter is incremented. When one goes out of scope, the reference counter is decremented. Only if the counter reaches zero is the resource freed) thus it will only be freed once, when the last shared_ptr owning the resource goes out of scope.
It's usually preferable to avoid explicitly writing out new and use make_shared, which does the allocation, creates the object for you and returns a shared_ptr that owns it:
auto object = std::make_shared<exampleClass>(60);
Some additional advanced reading in the topic, not strictly related to the question:
performance differences when using make_shared vs manually calling new: here
memory implications of using make_shared with weak_ptrs and large objects: here (Thanks for bringig this up #Yakk - Adam Nevraumont, this was new for me :))

Shared pointer references

In my code, I use a shared pointer using boost's shared_from_this feature. To keep it short, the shared pointer is shared on similar lines as below:
class Q: public enable_shared_from_this<Q>
{
public:
shared_ptr<Q> f()
{
return shared_from_this();
}
}
int main()
{
shared_ptr<Q> p(new Q);
shared_ptr<Q> q = p->f();
.....
.....
}
When I examine this in gdb:
(gdb) p *this
$8 = {
....
<boost::enable_shared_from_this<Q>> = {
weak_this_ = boost::weak_ptr<Q>(refs=0, weak=2) = {
px = (Q *) 0xa11f2000
}
}
....
}
What is the significance of 'refs' and weak' references here? Does refs=0 mean that there are no references to the object?
Thanks!
The control block for a shared_ptr holds two reference counts. One is the strong reference count, incremented once for each shared_ptr to the object. When it goes to zero the object is destroyed (and deallocated, unless it was allocated with make_shared).
The other is the weak reference count, incremented once as long as there are strong references, and again for each weak_ptr to the object. When it goes to zero, the control block is destroyed and deallocated.
So yes, in theory, the debugger display refs=0 means that there are no references to the object. Depending on where your program stopped, however, this sounds unlikely, in the program you show us, p lives until the end of main and should not give up its strong reference. It could be that the debugger's display is incorrect, especially if you compiled with optimizations.
Edit:
A weak count of 2 while refs is 0 means that all strong references are gone (all shared_ptr instances referring to the object have been destroyed), but there are 2 weak_ptrs remaining; unless the snapshot you're looking at is specifically within the destructor of the last shared_ptr, after decrementing the strong count but before decrementing the weak count, in which case it means there's only one weak_ptr left (which could be the one stored in enable_shared_from_this, if the snapshot is during your object's destructor).
Are you calling shared_from_this in Q's destructor and using the result without checking for null? shared_from_this doesn't work in constructors and destructors and always returns null. This could be the cause of the crash and would result in the values you observe.

boost::shared_ptr from pointer

I just stumbled on the boost::shared_ptr documentation, which goes:
Sometimes it is necessary to obtain a shared_ptr given a raw pointer
to an object that is already managed by another shared_ptr instance.
Example:
void f(X * p)
{
shared_ptr<X> px(???);
}
Inside f, we'd like to create a shared_ptr to *p.
In the general case, this problem has no solution.
Why? Is it not allowed to do something like:
shared_ptr<X> px(p);
Am I missing something?
If you have a shared_ptr managing a pointer and then create another shared_ptr managing the same pointer (NOT copying the original shared_ptr), you end up with two managers for the same resource. When one of the two reach a reference count of 0, it will delete the object and the other shared_ptr will point to deleted memory with all that follows.
If you would do this:
main() {
Object *obj = new Object();
func(obj)
}
void func( Object *obj ) {
shared_ptr objPtr(obj); // take ownership.
objPtr->fun();
// Passed object "obj" is destroyed here.
}
At the end of the function func the object pointer would get destroyed, and with the pointer the object itself. This wouldn't be a desirable behaviour.
Actually, you can do that, but you must know that the pointed object will be deleted when exiting the function...
I tried with boost:
void f(X * p)
{
boost::shared_ptr<X> px(p);
// do smething
}
void main()
{
X* ptr = new X();
f( ptr );
// ptr is not valid anymore because the object were deleted
}
Jean
You could do it, but it could lead to undefined behavior, since there is no way to tell the second shared pointer that the reference count (the number of shared pointers pointing at the same object) increased. Then things like this could happen:
void f()
{
boost::shared_ptr<int> firstSmart(new int(23)); // firstSmart is the only
// manager of the int
int *raw = firstSmart.get();
boost::shared_ptr<int> secondSmart(raw); // secondSmart also manages
// the same int as firstSmart
// but secondSmart does not
// know about firstSmart
// and vice versa
}
when f exits secondSmart gets destroyed, destroying the shared int. Then firstSmart gets destroyed and attempts to destroy the already destroyed int thus leading to undefined behaviour.
Its not possible to do this.
Shared pointers work using reference counting. When you assign a resource (raw pointer) to a shared pointer a reference count object is created with count =1. When another shared pointer is created for the same resource the reference count object (which is shared between both the shared pointers) is updated to value count =2. If one shared pointer is deleted the count in the shared reference object is decremented and when it reached 0 the resource is destroyed.
For the above mechanism to work the first shared pointer should be created using something like shared_ptr px(p) and all subsequent ones using px (not 'p'). This way all the shared pointers created will know that they are holding same resource and share the same reference count object.
If you created another shared pointer using shared_ptr px(p) then you end up with two shared pointer not related to each other - i.e there reference count objects are not same. They both assume that they are holding distinct resource and each of them have distinct (different) reference count object with count =1. (You don’t want this).

What's the difference between raw pointer and weak_ptr?

As in title. This question probably already has an answer but I failed to find one.
The fundamental conceptual difference between a naked pointer and a weak_ptr is that if the object pointed to is destroyed, the naked pointer won't tell you about it. This is called a dangling pointer: a pointer to an object that doesn't exist. They're generally hard to track down.
The weak_ptr will. In order to use a weak_ptr, you must first convert it into a shared_ptr. And if that shared_ptr doesn't point to anything, then the object was deleted.
For example:
#include <iostream>
#include <memory>
std::weak_ptr<int> wp;
void test()
{
auto spt = wp.lock(); // Has to be copied into a shared_ptr before usage
if (spt) {
std::cout << *spt << "\n";
} else {
std::cout << "wp is expired\n";
}
}
int main()
{
{
auto sp = std::make_shared<int>(42);
wp = sp;
test();
}
test();
}
Output
42
wp is expired
A raw pointer is (at least normally) simply an address. You can't tell anything about what it points at from the pointer itself.
A weak_ptr is always associated with a shared_ptr, so we probably need to start with a shared_ptr to make any sense of a weak_ptr.
A shared_ptr is reference counted, so it keeps track of how many references (pointers) to an object exist, and automatically destroys the object when no more references to that object exist.
As I already said, a weak_ptr is associated with a shared_ptr. Unlike a shared_ptr, the existence of a weak_ptr does not increment the reference count for the pointee object. To use a weak_ptr, you must first convert it to a shared_ptr. If the current reference count is positive, that will succeed, and converting the weak_ptr to a shared_ptr will increment the reference count to signify that the converted pointer is a "real" reference to the object. If, on the other hand, the reference count is already zero (meaning the pointee object has already been destroyed) the attempt to convert the weak_ptr to a shared_ptr will simply fail.
A shared_ptr means shared ownership of the pointee object. The pointee object will remain in existence as long as at least one shared_ptr to that object exists, but as soon as the last shared_ptr to the object is destroyed, so will the pointee object.
A weak_ptr means non-owning access to the pointee object. It allows access if the object exists. If the object has been destroyed, it tells you that the pointee object no longer exists rather than attempting to access the destroyed object.

shared_ptr and weak_ptr differences

I am reading Scott Meyers "Effective C++" book. It was mentioned that there are tr1::shared_ptr and tr1::weak_ptr act like built-in pointers, but they keep track of how many tr1::shared_ptrs point to an object.
This is known as reference counting. This works well in preventing resource leaks in acyclic data structures, but if two or more objects contain tr1::shared_ptrs such that a cycle is formed, the cycle may keep each other's reference count above zero, even when all external pointers to the cycle have been destroyed.
That's where tr1::weak_ptrs come in.
My question is how cyclic data structures make the reference count above zero. I kindly request an example C++ program. How is the problem solved by weak_ptrs? (again, with example please).
Let me repeat your question: "My question, how cyclic data structures makes reference count above zero, kindly request to show with example in C++ program. How the problem is solved by weak_ptrs again with example please."
The problem occurs with C++ code like this (conceptually):
class A { shared_ptr<B> b; ... };
class B { shared_ptr<A> a; ... };
shared_ptr<A> x(new A); // +1
x->b = new B; // +1
x->b->a = x; // +1
// Ref count of 'x' is 2.
// Ref count of 'x->b' is 1.
// When 'x' leaves the scope, there will be a memory leak:
// 2 is decremented to 1, and so both ref counts will be 1.
// (Memory is deallocated only when ref count drops to 0)
To answer the second part of your question: It is mathematically impossible for reference counting to deal with cycles. Therefore, a weak_ptr (which is basically just a stripped down version of shared_ptr) cannot be used to solve the cycle problem - the programmer is solving the cycle problem.
To solve it, the programmer needs to be aware of the ownership relationship among the objects, or needs to invent an ownership relationship if no such ownership exists naturally.
The above C++ code can be changed so that A owns B:
class A { shared_ptr<B> b; ... };
class B { weak_ptr<A> a; ... };
shared_ptr<A> x(new A); // +1
x->b = new B; // +1
x->b->a = x; // No +1 here
// Ref count of 'x' is 1.
// Ref count of 'x->b' is 1.
// When 'x' leaves the scope, its ref count will drop to 0.
// While destroying it, ref count of 'x->b' will drop to 0.
// So both A and B will be deallocated.
A crucial question is: Can weak_ptr be used in case the programmer cannot tell the ownership relationship and cannot establish any static ownership because of lack of privilege or lack of information?
The answer is: If ownership among objects is unclear, weak_ptr cannot help. If there is a cycle, the programmer has to find it and break it. An alternative remedy is to use a programming language with full garbage collection (such as: Java, C#, Go, Haskell), or to use a conservative (=imperfect) garbage collector which works with C/C++ (such as: Boehm GC).
A shared_ptr wraps a reference counting mechanism around a raw pointer. So for each instance of the shared_ptr the reference count is increased by one. If two share_ptr objects refer the each other they will never get deleted because they will never end up with a reference count of zero.
weak_ptr points to a shared_ptr but does not increase its reference count.This means that the underying object can still be deleted even though there is a weak_ptr reference to it.
The way that this works is that the weak_ptr can be use to create a shared_ptr for whenever one wants to use the underlying object. If however the object has already been deleted then an empty instance of a shared_ptr is returned. Since the reference count on the underlying object is not increased with a weak_ptr reference, a circular reference will not result in the underlying object not being deleted.
For future readers.
Just want to point out that explanation given by Atom is excellent, here is working code
#include <memory> // and others
using namespace std;
class B; // forward declaration
// for clarity, add explicit destructor to see that they are not called
class A { public: shared_ptr<B> b; ~A() {cout << "~A()" << endl; } };
class B { public: shared_ptr<A> a; ~B() {cout << "~B()" << endl; } };
shared_ptr<A> x(new A); //x->b share_ptr is default initialized
x->b = make_shared<B>(); // you can't do "= new B" on shared_ptr
x->b->a = x;
cout << x.use_count() << endl;
Weak pointers just "observe" the managed object; they don't "keep it alive" or affect its lifetime. Unlike shared_ptr, when the last weak_ptr goes out of scope or disappears, the pointed-to object can still exist because the weak_ptr does not affect the lifetime of the object - it has no ownership rights. The weak_ptr can be used to determine whether the object exists, and to provide a shared_ptr that can be used to refer to it.
The definition of weak_ptr is designed to make it relatively foolproof, so as a result there is very little you can do directly with a weak_ptr. For example, you can't dereference it; neither operator* nor operator-> is defined
for a weak_ptr. You can't access the pointer to the object with it - there is no get() function. There is a comparison function defined so that you can store weak_ptrs in an ordered container, but that's all.
All the above answer are WRONG. weak_ptr is NOT used to break cyclic references, they have another purpose.
Basically, if all shared_ptr(s) were created by make_shared() or allocate_shared() calls, you will NEVER need weak_ptr if you have no resource other than memory to manage. These functions create the shared_ptr reference counter object with the object itself, and the memory will be freed at the same time.
The only difference between weak_ptr and shared_ptr is that the weak_ptr allows the reference counter object to be kept after the actual object was freed. As a result, if you keep a lot of shared_ptr in a std::set the actual objects will occupy a lot of memory if they are big enough. This problem can be solved by using weak_ptr instead. In this case, you have to ensure the weak_ptr stored in the container is not expired before using it.