dimanche 20 août 2023

What's the best way to deal with circular object-owning references in a one/many-to-many C++ class associations?

Good day. I am facing a programming design task and am struggling with choosing the best approach for implementation due to lack of experience. I'd be glad to hear which practices, paradigms, or patterns are applicable to this case.

Problem Setting

You need to code, in C++98 standard, a Worker class that can own abstract Tools. Tools can exist on their own, hence can be equipped and unequipped by workers; tools can't have more than one worker owner.

However, what makes this trickier is the fact lifetimes of these objects need to be managed accordingly:

  • If a Tool is destroyed, it needs to get unequipped, since holding a pointer to a destroyed tool can lead to undefined behavior.
  • Similarly, if Worker is destroyed, the Tool should be disowned.
  • If a Tool is already given to a Worker, giving it to another Worker does remove the Tool from the original worker.

On top of that, suppose there's a class called Workshop. Workshops must accept only workers who have an available tool of a certain pre-determined type (I decided to make Workshop a template for a Tool subtype).

If a workshop enrolls a worker, a random available tool of correct type gets associated with it and becomes unavailable to other enrollments. Again,

  • If a Worker is destroyed, they leave the workshop.
  • If a Tool that a Worker is using in a Workshop gets destroyed or assigned to another worker, the Worker leaves the Workshop.

Questions

What is the best approach to this type of relationship?

Currently, in order to implement the tool disowning upon destruction, I store Tool pointers inside workers, and a Worker pointer inside Tools. ~Tool() calls a Worker method that unequips it. However, since I want to make it possible to destroy the relationship from both classes, say, have a Tool::unset_owner() and Worker::unequip(Tool*), both of them keep calling each other, resulting sometimes in infinite loops.

To add the Workshop support, I decided to create a WorkshopBase to hide its ToolType template, and hold an std::map<Tool*, WorkshopBase*> in Worker to register tools and workshops. But, it further results in chains of calling double-sided methods that attempt to remove/insert pointers from each other.

However, I feel like I'm missing something essential and am violating some best practices for such one-to-many + many-to-many relationships. So my question is:

  • How to avoid coupling in this case as much as possible?
  • I've seen many posts that claim this is a bad practice. I wonder if there's a design pattern to address this? Do you think an event system with management classes would be a better fit?
  • All in all, how to achieve my desired result gracefully?

Reminder: I'm looking for a C++98 solution. However, I would prefer to have answers with solutions with newer standards, with *_ptr<> classes usage, maybe, as a footnote.

Aucun commentaire:

Enregistrer un commentaire