C ++是否保证标准容器比较操作数的顺序?

时间:2017-07-10 14:19:25

标签: c++ c++11 c++-standard-library unordered-set

TL; DR:我有一个用例,其中重要的是调用WidgetEqualTo()(new_widget, widget_inside_container)WidgetEqualTo()(widget_inside_container, new_widget)

可以多次重新创建相同的小部件,因此我有WidgetPool(为了本示例的目的,是std::vector<const Widget*>的全局包装器)和智能构造函数:

const Widget* combine(const Widget* a, const Widget* b) {
  static std::unordered_map<std::pair<int, int>, int> cache;
  std::pair<int, int> ab = std::make_pair(a->id(), b->id());
  const auto it = cache.find(ab);
  if (it == cache.end()) {
    // The Widget ctor sets this->id() to WidgetPool::size()
    // and appends this to WidgetPool.
    const Widget* result = new Widget(a, b);
    cache[ab] = result->id();
    return result;
  } else {
    return WidgetPool::get_widget(it->second);
  }
}

我还有一个容器,其中Widgets按其创建顺序插入。比方说,std::unordered_set<const Widget*, WidgetHash, WidgetEqualTo>WidgetEqualTo看起来像这样:

struct WidgetEqualTo {
  bool operator()(const Widget* a, const Widget* b) const {
    if (a == b) {
      return true;
    }
    // My Widgets obey the associative law:
    // tedious_comparison(new Widget(new Widget(p, q), r),
    //                    new Widget(p, new Widget(q, r))) == true.
    const bool are_equal = tedious_comparison(a, b);
    if (are_equal) {
      // Cache the result of the comparison.
      // Retain the older Widget.
      if (a->id() < b->id()) {  // (***)
        WidgetPool::set_widget(b->id(), a);
        delete b;
      } else {
        WidgetPool::set_widget(a->id(), b);
        delete a;
      }
    }
    return are_equal;
  }
};

如果始终使用WidgetEqualTo()调用(new_element, element_already_inside_unordered_set)或相反,我可以删除标有(***)的测试的一个分支。 FWIW,libstdc ++似乎调用了WidgetEqualTo()(new_element, old_element)。 C ++标准是否保证了这种行为?

1 个答案:

答案 0 :(得分:11)

没有

  

[C++11: 25.2.5/3]:每个无序关联容器由Key参数化,由符合Hash要求(17.6.3.4)的函数对象类型Hash参数化并充当哈希函数用于Key类型的参数值,以及二元谓词Pred,它可以在类型Key 的值上引发等价关系。此外,unordered_mapunordered_multimap将任意映射类型TKey相关联。

表17告诉我们EqualityComparable要求:

  

==是等价关系,也就是说,它具有以下属性:

     
      
  • 适用于所有aa == a
  •   
  • 如果a == b,则b == a
  •   
  • 如果a == bb == c,则a == c
  •   

(gah!逗号拼接!)

请注意,比较器的给定语义没有提到操作数的具体方式:

  

[C++11: 25.2.5/5]:如果容器的k1函数对象在传递k2时返回Key,那么类型key_equal的两个值true和{{1}}将被视为等效值。 [..]

简单地说,如果提供参数的顺序很重要,那么你的程序会有未定义的行为。

这也不是C ++的奇怪之处; equivalence implies symmetry throughout mathematics