Objects storing data and objects storing smart pointers to data

That seems to be an interesting exercise in object design. Let's try something out naively.

class GalacticEmpire
{
    std::string m_coolName;
    std::string m_edgyTitleOfTheRuler;
    std::vector<Fleet> fleets;
};

That seems right - Empire owns it's own fleets, they are arranged in the container (vector), and we do not need to use any indirection here - vector stores Fleet objects.

Now, let's use view pointer in the StarSystem:

class StarSystem
{
    string m_name;
    color m_starColor;
    std::vector<Fleet*> fleets;
}

We will populate StarSystem::fleets with addresses from GalacticEmpire::fleet, and it seems to work at the first glance.

Unfortunately, this solution is extremely brittle. If Empire happens to add new fleets to it's force, it would do this by adding objects to GalacticEmpire::fleets vector and will invalidate the addresses to those stored in StarSystem::fleets. Not great!

Second attempt:

 class GalacticEmpire
 {
    // ...
    std::vector<std::unique_ptr<Fleet>> fleets;
 };

And StarSystem::fleets store (non-owning) pointer managed by unique_ptrs of GalacticEmpire::fleets. This solves us a problem of adding new fleets - even if pushing new elements to the vector ivalidates pointers to unique_ptrs, the pointers managed by said ptrs remain valid.

However, this solution has two drawbacks: you loosing performance. Objects which could be stored directly in the vector of fleets are now created dynamically. Accessing those requires indirection, and it all takes a heavy toll on performance.

The other problem is logical - we have solved a problem of adding new fleet, but what if the fleet is removed? We do need to clean-up this fleet from the StarSystem it expected to be!

Let's think for a moment. It is clear that a StarSystem can host multiple fleets, but a fleet can only be stationed within a single StarSystem. Let's use this information:

class Fleet
{
    string m_commander;
    int m_totalShips;
    StarSystem* stationed_system;
};

We add the pointer to the StarSystem this fleet is hosted to the fleet itself. Now, when an Empire looses one of it's fleets, we should be able to clear that fleet from the list of fleets stationed in the StarSystem. But, how do we find it? Iteratively in the vector? This is rather slow. Let's do unordered_set instead, so we will be able to find (and remove) a fleet in constant time!

class StarSystem
{
    std::unordered_set<Fleet*> fleets;
};

Now, the only thing left is to make sure we introduce a certain type of friendship between classes and add some private and public functions which would guarantee that anytime a fleet is removed it is also removed from it's StarSystem. This is left for the reader.

Tags:

C++