【问题标题】:C++ Stl Set BehaviorC++ Stl 设置行为
【发布时间】:2013-01-13 00:54:28
【问题描述】:

我试图运行以下代码。我发现输出存在差异。我了解比较器功能中使用的排序机制存在问题。我基本上在寻找的是: 1)Set如何在内部存储数据。 2)如何解决此问题或将数据复制到不同集合的最佳方法。 3) 排序究竟是如何造成这个问题的。

#include <iostream>
#include <set>
using namespace std;
struct Comparator {
  bool operator()( const int& a, const int& b ) {
    if( a <= b ) 
      return true;
    else
      return false;
  }
};
int main()
{
  set< int, Comparator > customSet;
  for( unsigned k = 0, index = 2; k < 10; ++k ) {
    customSet.insert( index );
  }

  set< int, Comparator >::iterator iter = customSet.begin();
  for(; iter != customSet.end(); ++iter ) {
    cout<<*iter<<endl;
  }

  cout<<"---------------------------------"<<endl;
  set< int, Comparator > tempCustomSet ;//= customSet;
  tempCustomSet.insert( customSet.begin(), customSet.end() );

  iter = tempCustomSet.begin();
  for(; iter != tempCustomSet.end(); ++iter ) {
    cout<<*iter<<endl;
  }

  return 0;
}

【问题讨论】:

  • 您的情况不安全。应该说,if ((a &lt;= b) == true ? true : false) { return a &lt;= b ? true : false } else return {!true &amp;&amp; false; }。一个常见的错误。

标签: c++ stl set equivalence strict-weak-ordering


【解决方案1】:

有关std::set 的更多详细信息,请参阅此reference。实现不应该让您担心(它们可能因平台而异),因为接口和复杂性保证对标准来说是最重要的。典型的实现是使用red-black-trees

您需要使您的Comparator 使用operator&lt; 而不是operator&lt;=。原因是如果!Comparator(a, b) &amp;&amp; !Comparator(b, a) 的计算结果为truestd::set 将认为元素等效(即,两者都不严格小于另一个)。

但是对于&lt;=,您的a &lt;= a 等于true,因此!(a&lt;=a) &amp;&amp; !(a&lt;=a) 为相等的元素提供false。而对于&lt;,您的a &lt; a 等于false,所以!(a&lt;a) &amp;&amp; !(a&lt;a) 给出true

正确的做法是:

struct Comparator 
{
    bool operator()(int const& lhs, int const& rhs) const 
    {
        return lhs < rhs; 
    }
};

这将保证相等的元素被认为是等价的。请注意,这在Effective STL,“第 19 项。理解相等和等价之间的区别”中进行了详细讨论。

【讨论】:

  • 但是说我有一个业务逻辑需要以这种方式/顺序将数据插入到集合中。
  • @user469258 你仍然可以使用你自己的比较,但你需要确保相等的元素是等价的。
  • @user469258:您的业务逻辑需求(不幸的是)无关紧要,它是严格的弱排序或高速公路。
  • @MatthieuM。对于任何a &lt;= b,您始终可以将a &lt; b 定义为!(b &lt;= a),因此他的业务逻辑始终可以安全地映射到std::set,即使他无法访问定义operator&lt;= 的代码。
  • @rhalbersma: 正好是a &gt; b,或者逆序。我的意思很简单,无论您首先采用何种业务逻辑,都必须符合set 的期望(显然,如果您使用set)。
【解决方案2】:

问题很可能是因为您的比较没有实现strict weak ordering。集合上的内部排序机制依赖于此。您可以通过将比较更改为小于来获得 SWO:

struct Comparator {
  bool operator()( const int& a, const int& b ) const {
    return ( a < b ); 
  }
};

另一方面,std::set 将默认使用此比较标准,因此您无需指定它。

在我对this question 的回答中有一些相关信息(以及无数其他 SO 问题)。

【讨论】:

    【解决方案3】:

    1) Set 内部如何存储数据

    唯一的要求是元素是:

    • 根据比较器排序,这样如果Comp(a,b),那么在迭代集合时a出现在b之前;
    • 独特,因此Comp(a,b)Comp(b,a) 都没有不同的元素。

    并且操作满足一定的复杂性要求。

    实际上,它们通常存储在二叉搜索树中;但这对用户来说并不重要。

    2) 我该如何解决这个问题或将数据复制到不同集合的最佳方法

    为了满足要求,比较器必须是严格的弱排序,比如&lt;,这样Comp(a,a)总是假的,而不是像@987654328这样的非严格排序@。由于&lt; 是默认值,这意味着您根本不需要自定义比较器。

    3) 排序究竟是如何造成这个问题的

    请注意,您的第一个循环将值 2 插入十次;我不确定这是不是故意的。

    鉴于所需的严格排序,insert(b) 可能会通过查找第一个元素a 来查找插入点,这样Comp(a,b) 为假;即b 不应该出现的第一个元素。然后它将通过检查Comp(b,a) 来检查唯一性。如果两者都为 false,则表示两个值相等,因此不会插入 b

    由于您的比较不严格,因此此唯一性测试可能会失败;所以你最终可能会得到一个重复的条目。否则可能会发生其他事情 - 行为未定义。

    【讨论】:

    • 但是迈克 3) 回答了在插入第一组的过程中它是如何正确地做到这一点的。这意味着如果您查看所有十个元素都已打印(第一个打印功能)。在这种情况下,2 也被复制了,这意味着不应该插入。
    • @user469258:因为未定义的行为是未定义的。对我来说,它不是“能够正确地做到这一点” - 运行代码会多次插入值 2,这对于集合来说肯定不是正确的行为,并且是比较器打破集合假设的直接结果。但是,如果比较器不严格,则不要求该集合无法以任何特定方式工作;取决于它的实现方式,某些操作有时可能会表现得像正确严格的排序一样。
    • 我同意这一点。但我仍然不清楚 Set 第一次是如何工作的。但是,当我使用临时集合并尝试插入从原始集合中获取的元素时,它会失败。如果订购必须失败,它应该首先失败。这意味着在打印第一组的元素时,它应该只打印两次“2”。
    • @user469258:无效的排序不会“失败”。 set 实现假设排序是有效的;如果不是,那么它具有未定义的行为,这意味着它可以做任何事情。插入单个项目时可能会得到与插入范围时不同的行为,这当然不足为奇。正如我所说,它不是第一次“工作”,至少对我来说 - 我得到十份 2,而“工作”集应该只包含一个。
    【解决方案4】:

    因为您是inserting in different ways,所以在两种情况下您会得到不同的输出。在案例 1 中,您将元素 2 插入十次。在这种情况下,当您在第一次之后插入整数 2 时,您的 Comparator() 函数将被调用以决定插入的位置。在另一种情况下,您正在插入一个范围。在这里,被调用的函数接受第一个参数,即customSet.begin(),并与另一个参数即customSet.end()进行检查,如果这两个不相等,则只插入一个元素,否则不会插入元素。

    【讨论】:

      猜你喜欢
      • 2010-09-21
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-03-16
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多