【问题标题】:Hashing polymorphic type the proper way散列多态类型的正确方法
【发布时间】:2017-08-22 16:08:22
【问题描述】:

我有一个使用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));
}

所以我需要为A 重载hash_append

template <class HashAlgorithm>
void hash_append(HashAlgorithm& h, A const& a) {
    hash_append(h, typeid(a)); 
}

这里的问题是,根据a 的运行时类型,我需要向哈希添加额外信息,例如对于C,我需要添加u

我想到了以下解决方案(和缺点):

  1. A 添加一个虚拟方法,该方法返回一个可以添加到typeid() 哈希的特定值,但是:

    • 这意味着在A 中添加一个与A 的目的无关的方法,因此我不太喜欢这个想法(特别是因为我有多个类似A 的类);李>
    • 这打破了hash_append 的概念,因为该方法将为所有继承类具有唯一的返回类型。
  2. hash_append里面做一堆dynamic_cast

    • 我觉得这很丑……特别是如果我有多个类似于 A 的类;
    • 这很容易出错:如果有人添加了 A 的新子代,并且不在 hash_append 内添加 dynamic_cast。

有没有办法散列一个多态类型,而不必修改类型本身或依赖一堆dynamic_cast


这样做的最终目标是能够记住一些繁重函数的结果。让我们勾勒一下我的应用程序的基本结构:

struct Input { };
struct Result { };

Result solve(Input const&);

solve 函数的计算量很大,所以我想使用Inputs 的哈希值将先前计算的结果保存在文件中,例如类似:

// 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;
}

loadstore 方法将从文件加载和存储结果,目标是在不同运行之间记忆解决方案。

如果您对如何在不必处理上述问题的情况下记忆这些结果有任何建议,我将很高兴阅读它们。

【问题讨论】:

  • 您可以在Ahash_append 版本中使用双重分派,并在您找回类型时将请求分派到BC 的正确版本,但我没有不要认为这正是您要寻找的。你考虑过吗?缺点是您必须向这些类添加样板才能接受访问者。如果它对您有用,我可以将评论放在更详细的答案中。
  • @skypjack 对不起,我不完全理解你的意思——你能写一个小例子来说明你的意思吗?
  • 我的意思是along this line。这不是一个可行的示例,但您应该明白这一点。不幸的是,您的示例代码缺少很多代码来打包一个具体的示例,我很抱歉。当然,HashVisitor 可以通过使用可变参数模板和直接继承来简化(至少,这样您就不必每次定义新类型时都修改它),但是我定义的方式应该更容易理解。
  • @skypjack 感谢您的示例。这是一个有趣的想法,至少它消除了我第一个想法的第二个缺点。你写它的方式,它看起来很健壮(如果我添加一个新类D,除非我向访问者添加D,否则它不会编译),但是是否可以简化HashVisitor 而不会丢失它鲁棒性?因为如果我去掉这个健壮性,这看起来类似于dynamic_cast 版本。
  • 如果您发现它是一个可行的解决方案,让我稍微修改一下并将其放入答案中。

标签: c++ hash polymorphism c++14


【解决方案1】:

您可以在Ahash_append 版本中使用双重调度,并将请求转发到正确的版本(即BC 的版本)。缺点是您必须在这些类中添加样板才能接受访问者,我不能说它是否适合您。
这是一堆应该说明这个想法的代码:

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);
}

【讨论】:

  • 感谢您的回答 - 如果我想要稳健性,我认为这是最好的答案,即没有具有未实现哈希方法的类型。不幸的是,我不得不采取相反的方向,即一个不太健壮的方法(在编译时)但约束较少:通常,我不能在访问者之前前向声明类,我仍然想将散列过程与自己上课。我正在尝试实现一些可以让我在声明类后添加自定义运行时调度的东西(我当时有点卡住,可能很快就会打开另一个问题......)。
  • 让我知道,如果可能的话,我会尽力给你答复。
  • @Holt 刚刚看到,抱歉,我正在度假。反正你几个小时前就删了。如果我迟到了,我很抱歉。 :-(
猜你喜欢
  • 2023-03-11
  • 2011-07-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-05-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多