C++ 减少运算符重载,使用哪种方式

c++ less operator overload, which way to use?

本文关键字:方式 运算符 重载 C++      更新时间:2023-10-16

例如:在C++头文件中,如果我定义了一个struct Record并且我想使用它进行可能的排序,以便我想重载less operator。以下是我在各种代码中注意到的三种方式。我粗略地注意到:如果我要把Record放进一个std::setmappriority_queue,......容器,版本 2 有效(可能版本 3 也可以(;如果我要Record保存到vector<Record> v中,然后打电话给make_heap(v.begin(), v.end())等。那么只有版本 1 有效。

  struct Record
  {
      char c;
      int num;
      //version 1
      bool operator <(const Record& rhs)
      {
         return this->num>rhs.num;
      }
      //version 2
      friend bool operator <(const Record& lhs, const Record& rhs) //friend claim has to be here
      {
         return lhs->num>rhs->num;
      }
  };

例如,在同一个头文件中:

      //version 3
      inline bool operator <(const Record& lhs, const Record& rhs)
      {
         return lhs->num>rhs->num;
      }

基本上,我想在这里抛出问题,看看是否有人可以提出一些总结,这三种方法之间有什么区别,每个版本的正确位置是什么?

它们本质上是相同的,除了第一个是非常量并允许您修改自身。

我更喜欢第二个,原因有两个:

  1. 它不一定是friend.
  2. lhs不一定是Record

定义 less 运算符的最佳方法是:

struct Record{
    (...)
    const bool operator < ( const Record &r ) const{
        return ( num < r.num );
    }
};

欢迎来到 c++20,我们有更多的选择。

  //version 1
  bool operator <(const Record& rhs)
  {
     return this->num>rhs.num;
  }

这个是错误的,它应该读作:

  //version 1
  bool operator <(const Record& rhs)const
  {
     return this->num>rhs.num;
  }

因为您希望左侧也符合 CONST 资格。

  //version 2
  friend bool operator <(const Record& lhs, const Record& rhs) //friend claim has to be here
  {
     return lhs->num>rhs->num;
  }

这个是对称的。 所以假设你有一个struct Bar operator Record.

然后

Record rhs;
Bar lhs;
assert( lhs < bar );

上述适用于对称情况,但不适用于成员函数版本。

类版本中的friend是一个运算符,只能通过 Koenig 查找(参数相关查找(找到。 当您希望将对称运算符(或类型位于右侧的运算符,如 ostream&<<*this(绑定到特定模板类实例时,它非常有用。

如果它在类之外,它必须是模板函数,并且模板函数的重载方式与非模板函数不同;非模板函数允许转换。

template<class T>
struct point {
  T x ,y;
  point operator-(point const& rhs)const{
    return {x-rhs.x,y-rhs.y};
  }
  friend point operator+(point const& lhs, point const& rhs) {
    return {lhs.x+rhs.x, lhs.y+rhs.y};
  }
};
template<class T>
point<T> operator*( point<T> const& lhs, point<T> const& rhs ) {
  return {lhs.x*rhs.x, lhs.y*rhs.y};
}

这里的-是不对称的,所以如果我们有一个类型转换为左侧的point<int>-将找不到。

+是对称的,是"Koenig 运算符",因此它是非模板运算符。

*是对称的,但是一个模板运算符。 如果你有转换为点的东西,它不会找到*重载,因为扣除会失败。

  //version 3
  inline bool operator <(const Record& lhs, const Record& rhs)
  {
     return lhs->num>rhs->num;
  }

这类似于上面的template,但这里不会出现该问题。 这里的区别在于,你可以在类外获取这个函数的地址,而你写的"koenig operator<"只能通过ADL找到。 哦,这不是朋友。

C++17 添加

 auto operator<=>(const Record&)=default;

我们使用宇宙飞船操作员<=>自动定义排序。

这将使用 cnum 的排序来生成所需的结果。

就像 5 法则一样,您应该设法使这里的=default正常工作。 声明<忽略是一种难闻的气味,纠缠您状态的不同部分也是如此。

    成员
  1. 函数的非成员等效项
bool operator <(const Record& rhs); 

bool operator <(Record& lhs, const Record& rhs);  // lhs is non-const

现在,STL 容器将它们存储的项目视为const(至少只要比较运算符是这样(。所以他们称const-const运算符的变体。如果他们找不到它(如果您仅提供变体 1( - 这是一个编译错误。

  1. 如果您同时提供const-const会员和const-const非会员:
    struct Record
    {
      bool operator <(const Record& rhs) const;
    };
    
    bool operator <(Record& lhs, const Record& rhs);

这是另一个编译器错误,因为这样的定义会导致歧义:

如果在找到匹配项的最高级别找到两个匹配项,呼叫因不明确而被拒绝。/斯特劳斯特鲁普,C++,第 12.3.1 节/

  1. 最后,(如前面的答案所述(不需要friend修饰符,因为默认情况下struct的所有字段都是公共的。

PS make_heap 不希望比较的项目const,因为它是一个更底层的野兽,使用它就像共同创作一个新的基于堆的库,所以你有责任跟踪项目的恒常性。

PPS set将项目视为const并不能防止您在将项目插入容器后修改项目的键 - 如果您尝试这样做,将导致运行时错误(段错误(。

在类中支持,除非它不能在类中,因为第一个参数是错误的类型。