我有一个课我希望能够比较平等。这个类很大(它包含一个位图图像),我会多次比较它,所以为了提高效率,我要对数据进行哈希处理,只检查哈希是否匹配。此外,我只会比较我的一小部分对象,所以我只是在第一次完成相等性检查时计算哈希值,然后使用存储的值进行后续调用。
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;
};
根据this answer的指导,等式运算符的规范形式是:
inline bool operator==(const X& lhs, const X& rhs);
此外,以下有关运算符重载的一般建议is given:
始终坚持运营商众所周知的语义。
我的函数必须能够改变它的操作数才能执行散列,所以我不得不将它们设为非const
。是否有任何潜在的负面影响(示例可能是标准库函数或STL容器,期望operator==
拥有const
个操作数?
如果变异operator==
函数被认为与其众所周知的语义相反,如果变异没有任何可观察的影响(因为用户无法看到散列的内容) )?
如果上述任何一个的答案都是“是”,那么什么是更合适的方法呢?
答案 0 :(得分:37)
它似乎是mutable
成员的完全有效的用例。您可以(并且应该)仍然使operator==
通过const引用获取参数,并为类提供哈希值的mutable
成员。
然后,您的类将具有哈希值的getter,该哈希值本身标记为const
方法,并且在第一次调用时延迟评估哈希值。这实际上是为什么mutable
被添加到语言中的一个很好的例子,因为它不会从用户的角度更改对象,它只是一个实现细节,用于在内部缓存昂贵操作的值。
答案 1 :(得分:12)
使用mutable
表示要缓存但不影响公共接口的数据。
你好,“变异”→mutable
。
然后根据逻辑 const
来思考,保证对象为使用代码提供的内容。
答案 2 :(得分:3)
您不应该在比较时修改对象。但是,此函数不会在逻辑上修改对象。简单的解决方案:使hash
可变,因为计算哈希是一种兑现形式。看到:
Does the 'mutable' keyword have any purpose other than allowing the variable to be modified by a const function?
答案 3 :(得分:1)
答案 4 :(得分:1)
是的,引入语义上出乎意料的副作用总是一个坏主意。除了提到的其他原因:总是假设您编写的任何代码将永远只有其他人甚至没有听说过您的名字,然后从这个角度考虑您的设计选择。
当有人使用您的代码库发现他的应用程序很慢并尝试优化它时,如果它在==重载内,他会浪费很多时间试图找到性能泄漏,因为他不期望它,来自从语义的角度来看,要做的不仅仅是简单的对象比较。在语义上廉价的操作中隐藏可能代价高昂的操作是一种糟糕的代码混淆形式。
答案 5 :(得分:0)
你可以走可变路线,但我不确定是否需要。您可以在需要时执行本地缓存,而无需使用mutable。例如:
#include <iostream>
#include <functional> //for hash
using namespace std;
template<typename ReturnType>
class HashCompare{
public:
ReturnType getHash()const{
static bool isHashed = false;
static ReturnType cachedHashValue = ReturnType();
if(!isHashed){
isHashed = true;
cachedHashValue = calculate();
}
return cachedHashValue;
}
protected:
//derived class should implement this but use this.getHash()
virtual ReturnType calculate()const = 0;
};
class ReadOnlyString: public HashCompare<size_t>{
private:
const std::string& s;
public:
ReadOnlyString(const char * s):s(s){};
ReadOnlyString(const std::string& s): s(s){}
bool equals(const ReadOnlyString& str)const{
return getHash() == str.getHash();
}
protected:
size_t calculate()const{
std::cout << "in hash calculate " << endl;
std::hash<std::string> str_hash;
return str_hash(this->s);
}
};
bool operator==(const ReadOnlyString& lhs, const ReadOnlyString& rhs){ return lhs.equals(rhs); }
int main(){
ReadOnlyString str = "test";
ReadOnlyString str2 = "TEST";
cout << (str == str2) << endl;
cout << (str == str2) << endl;
}
输出:
in hash calculate
1
1
你能否给我一个充分的理由来保持为什么保持isHashed作为成员变量是必要的,而不是让它在需要的地方?请注意,如果我们真的想要的话,我们可以进一步摆脱“静态”使用,我们所有的都是做一个专门的结构/类