我使用Howard Hinnant's method实现了哈希流程(基于hash_append
重载的通用哈希)。
该方法的目的是创建类的散列以“记住”计算结果(参见本答案的结尾),所以我面临一些问题。特别是,请考虑以下可能需要哈希的Input
类:
struct A {
virtual int do_stuff() const = 0;
virtual ~A();
};
struct B: A {
int do_stuff() const override { return 0; }
};
struct C: A {
const int u;
int do_stuff() const override { return u; }
};
struct Input {
A const& a; // store a reference to an instance of B or C
};
现在,如果我想哈希Input
,我会有类似的东西:
template <class HashAlgorithm>
void hash_append(HashAlgorithm& h, Input const& input) {
hash_append(h, typeid(input));
hash_append(h, typeid(input.a));
}
所以我需要hash_append
的{{1}}重载:
A
这里的问题是,根据template <class HashAlgorithm>
void hash_append(HashAlgorithm& h, A const& a) {
hash_append(h, typeid(a));
}
的运行时类型,我需要向哈希添加额外的信息,例如对于a
,我需要添加C
。
我想到了以下解决方案(和缺点):
向u
添加一个虚方法,返回可以添加到A
哈希的特定值,但是:
typeid()
内添加一个与A
的目的无关的方法,因此我不太喜欢这个想法(特别是因为我有多个A
- 喜欢上课;)A
的概念,因为该方法将为所有继承类提供唯一的返回类型。在hash_append
内做一堆dynamic_cast
:
hash_append
; A
的新子项,并且未在A
内添加dynamic_cast。 有没有办法散列多态类型,无需修改类型本身或依赖一堆hash_append
?
这样做的最终目标是能够记忆一些重要功能的结果。让我们勾勒出我的应用程序的基本结构:
dynamic_cast
struct Input { };
struct Result { };
Result solve(Input const&);
函数计算量很大,所以我想使用solve
s的散列将先前计算的结果保存在文件中,例如类似的东西:
Input
// depends on hash_append
std::string hash(Input const&);
Result load_or_solve(Input const& input) {
auto h = hash(input);
Result result;
if (exists(h)) { // if result exists, load it
result = load(h);
}
else { // otherwize, solve + store
result = solve(input);
store(h, result);
}
return result;
}
和load
方法会加载和存储文件的结果,目标是在不同的运行之间记住解决方案。
如果您有关于如何在不处理上述问题的情况下记下这些结果的建议,我将很乐意阅读它们。
答案 0 :(得分:4)
您可以在hash_append
A
版本中使用双重调度,并将请求转发到正确的版本(即B
或C
)。缺点是您必须在这些类中添加样板来接受访问者,我不能说它是否可以接受。
这里有一堆代码可以说明这个想法:
struct B;
struct C;
struct Visitor {
virtual void visit(const B &) = 0;
virtual void visit(const C &) = 0;
};
template<typename T, typename... O>
struct HashVisitor: T, HashVisitor<O...> {
template<typename U>
std::enable_if_t<std::is_same<T, U>::value> tryVisit(const U &u) {
T::operator()(u);
}
template<typename U>
std::enable_if_t<not std::is_same<T, U>::value> tryVisit(const U &u) {
HashVisitor<O...>::visit(u);
}
void visit(const B &b) override { tryVisit<B>(b); }
void visit(const C &c) override { tryVisit<C>(c); }
};
template<>
struct HashVisitor<>: Visitor {};
template<typename... F
auto factory(F&&... f) {
return HashVisitor<std::decay_t<F>>{std::forward<F>(f)...};
}
struct A {
virtual void accept(Visitor &) = 0;
virtual int do_stuff() const = 0;
virtual ~A();
};
struct B: A {
void accept(Visitor &v) override { v.visit(*this); }
int do_stuff() const override { return 0; }
};
struct C: A {
const int u;
void accept(Visitor &v) override { v.visit(*this); }
int do_stuff() const override { return u; }
};
template <class HashAlgorithm>
void hash_append(HashAlgorithm &, const B &) {
// do something
}
template <class HashAlgorithm>
void hash_append(HashAlgorithm &, const C &) {
// do something
}
template <class HashAlgorithm>
void hash_append(HashAlgorithm &h, const A &a) {
auto vis = factory(
[&h](const B &b){ hash_append(h, b); },
[&h](const C &c){ hash_append(h, c); }
);
a.accept(vis);
}