I want to implement a number of algorithms that work on a graph and return scores for node-pairs indicating whether those nodes are similar. The algorithms should work on a single node-pair and on all possible node-pairs. In the latter case a collection/matrix should be returned.
The algorithms derive from
class SimilarityAlgorithm {
public:
Base(const Graph& G);
virtual double run(node u, node v) = 0; // indices for nodes in the graph
virtual ScoreCollection& runAll() = 0;
}
Now the algorithms differ in memory usage. Some algorithms might be symmetric and the scores for (u, v) and (v, u) are identical. This requires different ScoreCollection-types that should be returned. An example would be a sparse-matrix and a triangular matrix that both derive from ScoreCollection
.
This would boil down to covariant return types:
class SpecificAlgorithm : SimilarityAlgorithm {
public:
double run(node u, node v);
// The specific algorithm is symmetric and thus uses a symmetric matrix to save memory
SymmetricScoreCollection& runAll();
}
Your design seems appropriate for the problem you describe.
Problem:
However, there is a problem with your SpecificAlgorithm
: runAll()
doesn't return the same type as the virtual function of the base class. So it won't be called (or more probably, your code won't compile because of a missing virtual function).
Solution:
Use also a polymorphic approach for the ScoreCollection
, by making SymmetricScoreCollection
a derived class of ScoreCollection
:
class SymetricScoreCollection: public ScoreCollection {
//define the member functions to access the values virtual
...
};
class SpecificAlgorithm : public SimilarityAlgorithm {
public:
double run(node u, node v);
// The specific algorithm is symmetric and thus uses a symmetric matrix to save memory
ScoreCollection& runAll();
};
In fact it's an application of the factory method pattern, with the following roles:
Additional remark:
Returning a reference to ScoreCollection
from runAll()
brings in some risks. Suppose sa
is a specific algorithm.
In the following statement :
ScoreCollection sc = sa.runAll();
sa.runAll()
returns a reference to a SymetricScoreCollection
, but it would copy the referred object to sc, making it a ScoreCollection. Slicing occurs, and polymorphism will fail to work.
The following statement would however succeed:
ScoreCollection& rsc = sa.runAll();
because rsc is a reference and it would still refer to the original SymetricScoreCollection
object returned by sa.runAll()
, and everything would work as designed.
You see that it's very easy to have unnoticed mistakes when returning references. I'd suggest to return a pointer instead of a reference.