Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Nested shared_ptr destruction causes stack overflow

Tags:

c++

This is a more of design problem (I know why this is happening, just want to see how people deal with it). Suppose I have a simple linked list struct:

struct List {
    int head;
    std::shared_ptr<List> tail;
};

The shared_ptr enables sharing of sublists between multiple lists. However, when the list gets very long, a stack overflow might happen in its destructor (caused by recursive releases of shared_ptrs). I've tried using an explicit stack, but that gets very tricky since a tail can be owned by multiple lists. How can I design my List to avoid this problem?

UPDATE: To clarify, I'm not reinventing the wheel (std::forward_list). The List above is only a simplified version of the real data structure. The real data structure is a directed acyclic graph, which if you think about it is just a lot of of linked lists with shared tails/heads. It's usually prohibitively expensive to copy the graph, so data sharing is necessary.

UPDATE 2: I'm thinking about explicitly traversing down the pointer chain and std::move as I go. Something like:

~List()
{
    auto p = std::move(tail);
    while (p->tail != nullptr && p->tail.use_count() == 1) {
        // Some other thread may start pointing to `p->tail`
        // and increases its use count before the next line
        p = std::move(p->tail);
    }
}

This seems to work in a single thread, but I'm worried about thread safety.

like image 946
Zizheng Tai Avatar asked Apr 14 '16 21:04

Zizheng Tai


People also ask

What happens when shared_ptr goes out of scope?

All the instances point to the same object, and share access to one "control block" that increments and decrements the reference count whenever a new shared_ptr is added, goes out of scope, or is reset. When the reference count reaches zero, the control block deletes the memory resource and itself.

Is shared_ptr thread-safe?

std::shared_ptr is not thread safe. A shared pointer is a pair of two pointers, one to the object and one to a control block (holding the ref counter, links to weak pointers ...).

Do you need to delete shared_ptr?

The purpose of shared_ptr is to manage an object that no one "person" has the right or responsibility to delete, because there could be others sharing ownership. So you shouldn't ever want to, either.

Why would you choose shared_ptr instead of Unique_ptr?

Use unique_ptr when you want to have single ownership(Exclusive) of the resource. Only one unique_ptr can point to one resource. Since there can be one unique_ptr for single resource its not possible to copy one unique_ptr to another. A shared_ptr is a container for raw pointers.


1 Answers

If you're having problems with stack overflows on destruction for your linked datastructure, the easiest fix is just to implement deferred cleanup:

struct Graph {
    std::shared_ptr<Graph>  p1, p2, p3;   // some pointers in your datastructure
    static std::list<std::shared_ptr<Graph>>   deferred_cleanup;

    ~Graph() {
        deferred_cleanup.emplace_back(std::move(p1));
        deferred_cleanup.emplace_back(std::move(p2));
        deferred_cleanup.emplace_back(std::move(p3));
    }
    static void cleanup() {
        while (!deferred_cleanup.empty()) {
            std::list<std::shared_ptr<Graph>>  tmp;
            std::swap(tmp, deferred_cleanup);
            tmp.clear(); } }
};

and you just need to remember to call Graph::cleanup(); periodically.

like image 70
Chris Dodd Avatar answered Oct 24 '22 02:10

Chris Dodd