I have a class which I want to be able to compare for equality. The class is large (it contains a bitmap image) and I will be comparing it multiple times, so for efficiency I'm hashing the data and only doing a full equality check if the hashes match. Furthermore, I will only be comparing a small subset of my objects, so I'm only calculating the hash the first time an equality check is done, then using the stored value for subsequent calls.
class Foo
{
public:
Foo(int data) : fooData(data), notHashed(true) {}
private:
void calculateHash()
{
hash = 0; // Replace with hashing algorithm
notHashed = false;
}
int getHash()
{
if (notHashed) calculateHash();
return hash;
}
inline friend bool operator==(Foo& lhs, Foo& rhs)
{
if (lhs.getHash() == rhs.getHash())
{
return (lhs.fooData == rhs.fooData);
}
else return false;
}
int fooData;
int hash;
bool notHashed;
};
According to the guidance on this answer, the canonical form of the equality operator is:
inline bool operator==(const X& lhs, const X& rhs);
Furthermore, the following general advice for operator overloading is given:
Always stick to the operator’s well-known semantics.
My function must be able to mutate it's operands in order to perform the hashing, so I have had to make them non-const
. Are there any potential negative consequences of this (examples might be standard library functions or STL containers which will expect operator==
to have const
operands)?
Should a mutating operator==
function be considered contrary to its well-known semantics, if the mutation doesn't have any observable effects (because there's no way for the user to see the contents of the hash)?
If the answer to either of the above is "yes", then what would be a more appropriate approach?
It seems like a perfectly valid usecase for a mutable
member. You can (and should) still make your operator==
take the parameters by const reference and give the class a mutable
member for the hash value.
Your class would then have a getter for the hash value that is itself marked as a const
method and that lazy-evaluates the hash value when called for the first time. It's actually a good example of why mutable
was added to the language as it does not change the object from a user's perspective, it's only an implementation detail for caching the value of a costly operation internally.
Use mutable
for the data that you want to cache but which does not affect the public interface.
U now, “mutate” → mutable
.
Then think in terms of logical const
-ness, what guarantees the object offers to the using code.
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