Description
Background
In the PGM core we use std::shared_ptr
in many places. Sometimes the usage is justified, but also at many places, the usage is not justified.
Shared ownership
std::shared_ptr
is meant to be used for shared ownership. That is, there are multiple owners of the same resources, and we as library developer cannot determine in run-time which owner will be destroyed first. An example is shown below:
The ComponentTopology
will be constructed after the model adds all the components. It cannot be changed. When the user copies the model (via C or Python API), the same ComponentTopology
will be shared between multiple instances of MainModel
. We as PGM developer cannot possibly know the ownership hierarchy (if any) of difference copies of the MainModel
. Therefore, using a std::shared_ptr
is justified.
Similar applies to MathModelTopology
and TopologicalComponentToMathCoupling
. When a model has cached math topology, and it get copied. The same topology can be shared between different instances. When one of the copied model refreshes the topology, the ownership of the old topology from that instance is released. But the old topology can still be owned by other copies, until all the copies of the model refresh the topology.
Top-down logical flow
There are many places, however, where the logical flow is strictly top-down. In these places, usage of std::shared_ptr
is not justified. An example is shown below:
The IterativePFSolver
should not own the resource of phase_shift
(which is part of MathModelTopology
). In the logical flow, if the current the MathModelTopology
is destroyed, so will IterativePFSolver
also be destroyed. It does not make sense to let IterativePFSolver
to own this resource. This just create extra useless abstraction. It also complicates writing the test code. Now we have to create a shared resource in the test code for the class to consume.
Proposal
We propose to remove the std::shared_ptr
when shared ownership is not needed, i.e., we can know strictly that the resource will outlive the scope of the object by looking at the logical flow. We can change those members into one of following alternatives:
- Raw pointer: this is most straightforward to apply.
- Reference member: this can be interesting, but it makes the class not assignable.
- For
vector
like stuff, we can use astd::span
.
The choices is still under discussion.