Should I assume weak pointer usage if there is any cyclic relationship?

Viewed 85

Say I've a Person class, with name, age, and bestFriend as properties. I'm confused about how to best represent bestFriend. Multiple Person instances can be pointing to the same bestFriend, which rules out unique_ptr. And there is also a possibility of a cyclic relationship, which rules out shared_ptr. So, is using weak_ptr ideal in this case? Would I even want to use smart pointers here instead of just raw pointers?

class Person {
    std::string name;
    int age;
    std::weak_ptr<Person> bestFriend;
};
2 Answers

Pointers (both raw and smart) express certain ownership. Thus, to pick a proper pointer, you need to decide on the ownership. If something owns an object, then if this something dies, the object has to die too. Does my best friend has to die if I die? I don't think so. Thus, I don't own my best friend. This rules out unique_ptr and shared_ptr, since those are owning smart pointers.

So we have two choices: weak_ptr or a raw pointer. This depends on how you are going to keep track of alive persons.

The simplest approach might be to rely on smart pointers to keep track of alive persons. This means, you have some storage of shared_ptr to all persons, and then you store weak_ptr to point to them from another persons. In this case I know that if I have a friend and weak_ptr::lock() returns nullptr, this means my friend is dead. Convenient, but not very flexible. E.g. imagine you need to issue a letter to a family when a person dies. When would you do this? In person's destructor? This would be mix of responsibilities. In a custom deleter of a shared_ptr<Person>? That would break encapsulation, since this custom deleter might have many functions.

Another approach is to keep track of alive persons by a dedicated manager. Again, you need some storage where all persons live. You can store them by value, or via shared_ptr - up to you. Then you store raw pointers to those objects. (Note: if you store them in a vector, which size is an object to change, storing raw pointer to them is not a good idea, because of possible reallocation). In this case you cannot tell by looking on a raw pointer, whether my best friend is still alive or not. We would need to check the manager to get this information. This is more flexible, but I'd say less safe, since we have dangling pointers built-in in our system.

I'd recommend not to store raw pointers for that's sake, but to go for an ID. Store person's ID instead, and have a registry of persons by ID. This registry will handle all the mess and will properly detect situation, when somebody wants to access a dead person, trigger a post-man to issue a letter to relatives, etc. This also allows to distinguish situations when I don't have a best friend and when my best friend is dead. weak_ptr would return nullptr in both of these situations.

PS If we were storing shared_ptr to best friends, that would mean a person would be alive only as long as they are someone's best friend... So true, but not in software development world, sorry.

The smart pointers std::unique_ptr<> and std::shared_ptr<> are used for unique or shared ownership of the object pointed to. In the case of your Person, this concept of ownership hardly makes sense and the best friend is presumably best represented by a simple raw (observing) pointer:

struct Person
{
    std::string name;
    date_type born, died=date_type::never;
    const Person *bestFriend = nullptr;     // defaults to none

    time_type age() const
    { died==date_type::never? date_type::today()-born : 0; }
};

Having said that, you must ensure that these pointers remain valid at all times. To this end, the Persons could be stored using a std::deque<Person> which allows to add new Persons w/o invalidating any existing Person pointers. However, you cannot remove an arbitrary Person w/o invaliding pointers to other Persons, so better mark a Person as dead than to remove it (you may have another list of all living Person pointers). This has the advantage to avoid dangling best-friend pointers (but you will get best friends who are dead).

Warning. As you have failed to provide more detail of the intended purpose of class Person, the above suggestions are based on some speculation and may not actually be useful, depending on your actual intent.

Related