Let's say I want to organise fleets in my galactic empire.
class Fleet
{
string m_commander;
int m_totalShips;
}
class GalacticEmpire
{
string m_coolName;
string m_edgyTitleOfTheRuler;
/*Problem line number1*/
}
GalacticEmpire
is an object that will own and control everything that is solely of my empire - it will store all the bytes of its data.
But my GalacticEmpire
does not exist in void (I mean technically it does), there are StarSystem
s it controls.
class StarSystem
{
string m_name;
color m_starColor;
/*Problem line number2*/
}
Now StarSystem
is not supposed to store any raw data - it only has to be able to point to Fleet
s that station within its bounds.
And now the question.
How GalacticEmpire
should store Fleet
s so that it can access them and destroy them (entirely from program) and simultaneosly how StarSystem
should be able to point to Fleets
s so that it can iterate over them?
In the other words, how should Problem line number1
and Problem line number2
look like?
Option #1:
Problem line number1 = vector<Fleet> m_fleets
Problem line number2 = vector<shared_ptr<Fleet>> m_fleets
Option #2:
Problem line number1 = Problem line number2
= vector<shared_ptr<Fleet>> m_fleets
A pointer is a type of variable that carries location information. In this case, the example variable will store the address of an Order object that we want to interact with. We initialize the pointer variable by using the C++ new operator to construct a new object of type Order.
Smart pointers are used to make sure that an object is deleted if it is no longer used (referenced). The unique_ptr<> template holds a pointer to an object and deletes this object when the unique_ptr<> object is deleted.
It is a container of raw pointer and a reference counting (a technique of storing the number of references, pointers or handles to a resource such as an object, block of memory, disk space or other resources) ownership structure of its contained pointer in cooperation with all copies of the shared_ptr.
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_ptr
s of GalacticEmpire::fleets
. This solves us a problem of adding new fleets - even if pushing new elements to the vector ivalidates pointers to unique_ptr
s, 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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With