How to avoid memory leak with shared_ptr?
Asked Answered
V

1

32

Consider the following code.

using boost::shared_ptr;
struct B;
struct A{
    ~A() { std::cout << "~A" << std::endl; }
    shared_ptr<B> b;    
};
struct B {
    ~B() { std::cout << "~B" << std::endl; }
    shared_ptr<A> a;
};

int main() {
    shared_ptr<A> a (new A);
    shared_ptr<B> b (new B);
    a->b = b;
    b->a = a;

    return 0;
}

There is no output. No desctructor is called. Memory leak. I have always believed that the smart pointer helps avoid memory leaks.

What should I do if I need cross-references in the classes?

Valaree answered 1/12, 2009 at 15:24 Comment(0)
H
62

If you have circular references like this, one object should hold a weak_ptr to the other, not a shared_ptr.

From the shared_ptr introduction:

Because the implementation uses reference counting, cycles of shared_ptr instances will not be reclaimed. For example, if main() holds a shared_ptr to A, which directly or indirectly holds a shared_ptr back to A, A's use count will be 2. Destruction of the original shared_ptr will leave A dangling with a use count of 1. Use weak_ptr to "break cycles."

Thanks, Glen, for the link.

Headmistress answered 1/12, 2009 at 15:28 Comment(7)
@Alexey, here's a link to the docs where it explicitly warns of this problem in the introduction. boost.org/doc/libs/1_41_0/libs/smart_ptr/shared_ptr.htmEel
Which one? And why not replace both with weak references? This is ridiculous. shared_ptr was used for a reason.Avner
@curiousguy: I'm not sure I understand your question: what do you find to be ridiculous? To break a cycle, you must replace one strong reference with a weak reference; which one depends entirely on the use case. You can't replace all of the strong references with weak references, because then all of the objects will be destroyed as there are no owners left.Headmistress
@JamesMcNellis "what do you find to be ridiculous?" This idea that a reference can be replaced by a non-reference (called "weak" reference). This is insane.Avner
@Avner It isn't insane at all. Consider when the shared_ptr a and b go out of scope, they both decrease their internal reference counts. The count never reaches zero, since the each still have count of 1 from each other. The destructor is NEVER called because the count never reaches zero. They are holding each other hostage. One or the other has to yield ownership; you choose which one with the weak reference. This works with even larger number of links in the chain. Just, one of the links in the chain has to be weak to break the cyclic dependency.Pythian
@SnappleLVR The proposed solution is insane. The smart ptr is working as needed, as the semantics of owning ptr implies that the referenced object isn't destroyed before the owner. IOW, the design implies the objects cannot be automatically destroyed. "one of the links in the chain has to be weak" which one?Avner
The original design implies that the objects cannot be automatically destroyed. The revised solution guarantees destruction of every object. The problem is that the weak pointer can now point to an object that does not exist. The example itself as toy code is working fine with these revisions but something more complicated is a completely different topic. I wouldn't program something like this but the answer is correct and easily provable. FYI: didn't know that pressing enter does not go to newline but posts. I was going to write more above, obviously. :)Pythian

© 2022 - 2024 — McMap. All rights reserved.