Ælex
Ælex

Reputation: 14869

solution to a std::vector of std::shared_ptr leaking due to circular reference

I've gotten myself into a pickle: I'm coding a tree graph structure, where each layer contains a vector of pointers to previous layers (super types) and pointers to following (sub types) layers.

As you can imagine, this is a circular reference:

[last] -(super)-> [current]
[current] -(sub)-> [last]

When I allocate a new layer I make sure I move it to current and when I re-assign last to current, again I move it.

However, this doesn't solve the leak, in fact the only solution I've found is using raw pointers.

Here's a minified example omitting large parts of the code:

struct layer
{
    std::unordered_set<std::string> words;
    std::vector<layer*> sub_classes;
    std::vector<layer*> super_classes;
}

The actual iteration is of wordnet senses which populate a tree graph. The function that does the hypernyms (e.g., super-types of a word) is:

void iterate_sense(
                        Synset * sense,
                        std::shared_ptr<layer> last,
                        graph & rhs,
                        int lexical
                      )
    {
        std::shared_ptr<layer> current;
        while (sense && last)
        {
            current = std::move(get_layer(sense));
            last->super_classes.push_back(current.get());
            current->sub_classes.push_back(last.get());
            if (sense->ptrlist)
            {
                iterate_sense(sense->nextss, 
                              last,
                              rhs, lexical);
            }
            last = std::move(current);
            sense = sense->ptrlist;
        }
    }

Those pointers are owned by another class graph. When I use in layer

    std::vector<std::shared_ptr<layer>> sub_classes;
    std::vector<std::shared_ptr<layer>> super_classes;

Valgrind reports lost bytes.

1

What is the correct or elegant way of solving this? I assume that using raw pointers is not the best solution?

2

Am I correct to assume that the moment the owner graph is lost, the raw pointers will become invalid? This isn't much of a problem because layers themselves reside only in graphs.

3

I've read around SO and google that the correct usage is via combination of std::weak_ptr and std::shared_ptr however I don't understand how a weak_ptr would be used in such a case.

Upvotes: 0

Views: 56

Answers (1)

Gene
Gene

Reputation: 420

1

Smart pointers, are about owner ships. It says who is responsible for a certain Object. Semantic is basically split as follows:

  • std::unique_ptr<>: one function/object is the owner and responsible of the object.
  • std::shared_ptr<>: several function/objects are the owner and responsible of the object.
  • raw pointer: someone else is owner and responsible of the object.

In your case I would recommend following semantic: A Layer is the owner of its child layers. A Child is not the owner of its parent.

Following this I'd use

std::vector<std::unique_ptr<layer>> sub_classes;
std::vector<layer*> super_classes;e

For iterate_sense I'd suggest to use following signature:

void iterate_sense(
                    Synset * sense,
                    layer* last,
                    graph & rhs,
                    int lexical
                  )

This function is not fully or partially owning the object in variable last. It only is granted limited access to it.

2

Not sure in which way graph is owning these pointers. If graph is actually the owner with std::unique_ptr<> you can use only raw pointers in layers. This is exactly the correct way to use it.

3

In some cases it is possible to use std::weak_ptr<> to break circular references. I can not recommend using them for this. If you have a clean design of your data structures, it usually is very easy to say who is responsible/owner of a object. There are not that many cases where it is not clear. In these unclear cases, where more then an object is management/owned by several other objects you need std::shared_ptr<>. Under these circumstances you might need access to an object of which you don't know if it still exist. In this case you might want to use std::weak_ptr<>.

Upvotes: 2

Related Questions