【问题标题】:Modify internal structure when implementing std::hash<T>实现 std::hash<T> 时修改内部结构
【发布时间】:2016-07-31 17:14:59
【问题描述】:

我正在编写一个自定义的OrderedTree 类,我想将其用作unordered_set 的键。

在对树进行哈希处理时,我想做几件事:

  1. 懒惰地计算哈希并根据需要缓存(因为这可能是一项昂贵的操作),
  2. 也许平衡树。

这些操作都不会改变对象的语义相等或哈希值,但它们确实会修改一些私有字段。

不幸的是,尝试在 std::hash&lt;Tree&gt;::operator() 内部修改 OrderedTree 中的任何成员似乎违反了 unordered_set 所期望的 const 正确性。

我可以将OrderedTreeunordered_set 一起使用吗?如果有,怎么做?

编辑:

根据 cmets 中的要求,最低限度的概念证明:

#include <unordered_set>

std::size_t hash_combine(std::size_t a, std::size_t b) {
  // TODO: Copy from boost source or something
  return 0;
}

struct Node {
  int value;
  Node *left, *right, *parent;

  std::size_t hash(std::size_t seed) const {
    if (left != nullptr)
      seed = left->hash(seed);
    std::hash<int> hasher;
    seed = hash_combine(seed, hasher(value));
    if (right != nullptr)
      seed = right->hash(seed);
    return seed;
  }
};

struct Tree {

  Tree(): hash_(0), root(nullptr) {}

  Node *root;

  std::size_t hash() const {
    if (hash_ == 0 && root != nullptr) {
      hash_ = root->hash(7);
    }
    return hash_;
  }

private:
  std::size_t hash_;
};

namespace std {
  template<>
  struct hash<Tree> {
    std::size_t operator()(const Tree& t) const {
      return t.hash();
    }
  };
}

int main() {
  std::unordered_set<Tree> set;
}

当我尝试编译时,我得到:

Sample.cc:31:13: error: cannot assign to non-static data member within const member function 'hash'
      hash_ = root->hash(7);
      ~~~~~ ^
Sample.cc:29:15: note: member function 'Tree::hash' is declared const here
  std::size_t hash() const {
  ~~~~~~~~~~~~^~~~~~~~~~~~

【问题讨论】:

  • “似乎违反了 const 正确性”是如何表现出来的?这只是一些普遍的焦虑,还是您不知道如何实现它,或者您是否遇到编译器错误?
  • 听说过关键字“可变”吗?
  • @JVApen 我以前没有。谢谢!这似乎正是我正在寻找的。如果您发布答案,我会接受。

标签: c++ c++11 hash constants unordered-set


【解决方案1】:

保证标准容器只会在执行const 或逻辑上的const 操作时调用const 成员。如果那些const 操作是多读者安全的,那么容器也是如此;相反,如果不是,则容器也不是。

哈希值的不变性和相等性(或有序容器上的&lt;)是您在关联容器中的键类型中需要保证的唯一项。实际的const 提供了上述多读者保证,这可能非常有用。更重要的是,违反它会让你在未来使用它,和/或当有人认为 const 意味着不可变时微妙的但是。

你可以小心地在内部同步写操作以保持多读保证,或者你可以放弃它。

要违反const,通常使用mutable。如果对象是实际上 const,而不仅仅是非const 对象的const 视图,则使用强制转换绕过constconst 方法会冒未定义行为的风险。

一般来说,在使用这种优化之前要小心;它可以很容易地增加代码复杂性(提高错误、维护等),而不是增加速度。加速代码是可替代的:确保在投资之前将其识别为代码,并将这部分识别为瓶颈。如果你要平衡哈希,为什么要等待哈希?插入前平衡!

【讨论】:

  • 当您说“使用强制转换绕过...的 const 方法”时,您的意思是 const_cast 对吗?带有mutable 的程序仍然表现良好?我同意你对const 的态度,我不确定我是否会真正使用mutable,但我想了解更多有关危险功能的信息,以便在我真的需要时使用它们。
  • @math const_cast 或等效的 C 样式转换,是的。在声明为const(而不仅仅是const&amp; 到非const)上违反const 是UB。 mutable 成员在这个意义上不违反 const,即使在 const 方法中修改。
猜你喜欢
  • 2011-03-10
  • 1970-01-01
  • 1970-01-01
  • 2015-03-13
  • 2020-08-04
  • 2014-03-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多