【问题标题】:Strange Behavior of std::set and < Operator Overloading?std::set 和 < 运算符重载的奇怪行为?
【发布时间】:2017-10-30 03:52:25
【问题描述】:

我了解,如果 &lt; 运算符在 C++ 中被重载(例如,将自定义结构插入到 std::set),则实现必须是基础类型之上的 strict weak order

考虑以下struct 和实现。这个实现不是严格的弱顺序,但是代码编译和运行不会抛出错误(考虑到严格的弱顺序的要求,我希望它会抛出错误):

#include <iostream>
#include <set>
using namespace std;

struct Pixel {
    int x;
    int y;
};

bool operator < (Pixel lhs, Pixel rhs){
    return lhs.x < rhs.x || lhs.y < rhs.y;
};

int main(){
    set<Pixel> mySet;

    Pixel *newPixelA = new Pixel;
    newPixelA->x = 1;
    newPixelA->y = 3;

    Pixel *newPixelB = new Pixel;
    newPixelB->x = 4;
    newPixelB->y = 2;

    mySet.insert(*newPixelA);
    mySet.insert(*newPixelB);

}

这是预期的行为吗?编辑:使用 Xcode。

【问题讨论】:

  • 未定义的行为是未定义的。 “似乎有效”是未定义行为的一种可能表现形式。
  • 您的“似乎有效”确实是对您观察到的内容的准确描述。还不清楚为什么要动态分配源对象以进行插入。
  • 尝试超过 2 个项目。Visual Studio 在调试版本中立即抛出异常。
  • 既然你期望它会抛出一个错误而它不会,你为什么说它“看起来工作得很好”?难道它没有做你应该期望它做的与正常工作相反的事情吗?
  • 简短回答:如果您的比较函数不符合要求,则您的代码具有未定义的行为。您可能会收到某种错误消息,或者它似乎可以工作,或者几乎是其他任何东西。该标准对结果没有任何要求。

标签: c++ algorithm sorting c++11 set


【解决方案1】:

编译器无法确定您的operator&lt; 是否是严格的弱排序。相反,std::set 要求这样做的意思是,只有给它一个严格的弱排序,它才能正常工作。如果你给它其他东西,它不保证会发生什么。

一般来说,当 C++ 需要某些东西时,它的意思是有责任确保某事发生。如果你这样做了,那么编译器和库将保证你得到正确的结果。

【讨论】:

    【解决方案2】:

    如果满足比较器要求,标准可保证预期行为。否则,会发生什么取决于实现和数据集。您的比较函数可能适用于某些数据集(对于所有点,更大的 x 意味着更大的 y)。 Set 不能包含相等的元素(作为一个数学概念),对于std::set,等价意味着相等,所以如果已经有值a,它只会阻止您插入值b,这样:

    a < b == true
    b < a == true
    

    即使a 可能不等于b

    【讨论】:

      【解决方案3】:

      当比较运算符对包含的元素执行严格的弱排序时,std::set 中的对象以可预测的模式排序。如果不是,则当您遍历对象时,不知道哪个对象首先出现在 std::set 中。

      以下面的示例程序为例,其中Pixel1 的排序不正确,Pixel2 的排序正确。

      #include <iostream>
      #include <set>
      
      struct Pixel1 {
          int x;
          int y;
      };
      
      bool operator < (Pixel1 lhs, Pixel1 rhs){
          return lhs.x < rhs.x || lhs.y < rhs.y;
      };
      
      struct Pixel2 {
          int x;
          int y;
      };
      
      bool operator < (Pixel2 lhs, Pixel2 rhs){
          if ( lhs.x != rhs.x )
          {
             return (lhs.x < rhs.x);
          }
          return (lhs.y < rhs.y);
      };
      
      template <typename Pixel> void print(std::set<Pixel> const& mySet)
      {
         for ( Pixel p : mySet )
         {
            std::cout << "(" << p.x << ", " << p.y << ") ";
         }
         std::cout << std::endl;
      }
      
      template <typename Pixel> void test1()
      {
         std::set<Pixel> mySet;
      
         Pixel pixelA = {2, 3};
         Pixel pixelB = {4, 2};
         Pixel pixelC = {4, 1};
      
         mySet.insert(pixelA);
         mySet.insert(pixelB);
         mySet.insert(pixelC);
      
         print(mySet);
      }
      
      template <typename Pixel> void test2()
      {
         std::set<Pixel> mySet;
      
         Pixel pixelA = {2, 3};
         Pixel pixelB = {4, 2};
         Pixel pixelC = {4, 1};
      
         mySet.insert(pixelB);
         mySet.insert(pixelA);
         mySet.insert(pixelC);
      
         print(mySet);
      }
      
      int main()
      {
         std::cout << "Pixel1 ... \n";
         test1<Pixel1>();
         test2<Pixel1>();
      
         std::cout << "Pixel2 ... \n";
         test1<Pixel2>();
         test2<Pixel2>();
      }
      

      输出

      Pixel1 ... 
      (4, 1) (4, 2) (2, 3) 
      (4, 1) (2, 3) (4, 2) 
      Pixel2 ... 
      (2, 3) (4, 1) (4, 2) 
      (2, 3) (4, 1) (4, 2) 
      

      std::set&lt;Pixel1&gt; 中对象的顺序取决于插入顺序,而std::set&lt;Pixel2&gt; 中对象的顺序与插入顺序无关。

      只有你自己才能判断这在你的应用程序中是否可以接受,

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-03-04
        • 2013-11-23
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多