在Google C++ Style Guide中,运算符重载部分建议不要重载任何运算符(“除非在罕见的特殊情况下”)。具体来说,它建议:
特别是,不要重载
operator==
或operator<
只是为了让你的类可以用作 STL 容器中的键;相反,您应该在声明容器时创建相等和比较函子类型。
我对这样的仿函数看起来有点模糊,但我的主要问题是,你为什么要为此编写自己的仿函数?定义operator<
和使用标准std::less<T>
函数不会更简单吗?使用其中一个有什么优势吗?
在Google C++ Style Guide中,运算符重载部分建议不要重载任何运算符(“除非在罕见的特殊情况下”)。具体来说,它建议:
特别是,不要重载
operator==
或operator<
只是为了让你的类可以用作 STL 容器中的键;相反,您应该在声明容器时创建相等和比较函子类型。
我对这样的仿函数看起来有点模糊,但我的主要问题是,你为什么要为此编写自己的仿函数?定义operator<
和使用标准std::less<T>
函数不会更简单吗?使用其中一个有什么优势吗?
除了更基本的类型之外,小于运算并不总是微不足道的,甚至相等也可能因情况而异。
想象一家航空公司想要为所有乘客分配一个登机号码的情况。这个数字反映了登机顺序(当然)。现在,是什么决定了谁先于谁?您可能只采用客户注册的顺序 - 在这种情况下,小于操作将比较签入时间。您还可以考虑客户为他们的门票支付的价格——低于现在比较门票价格。
… 等等。总而言之,operator <
在Passenger
类上定义一个没有意义,尽管它可能需要将乘客放在一个排序的容器中。我认为这就是谷歌所警告的。
一般来说,定义operator<
更好更简单。
您想要使用仿函数的情况是当您需要多种方法来比较特定类型时。例如:
class Person;
struct CompareByHeight {
bool operator()(const Person &a, const Person &b);
};
struct CompareByWeight {
bool operator()(const Person &a, const Person &b);
};
在这种情况下,可能没有一个好的“默认”方式来比较和排序人们,因此不定义operator<
和使用仿函数可能会更好。你也可以说一般人是按身高排序的,所以operator<
只需调用CompareByHeight
,任何需要 Person 的按体重排序的人都必须CompareByWeight
明确使用。
很多时候,问题在于定义函子是由类的用户决定的,因此,每当需要在有序容器中使用类时,您往往会获得对同一事物的许多重新定义。
好吧,根据您引用的网页,函子没有太多优势(“[操作员]可以欺骗我们的直觉,认为昂贵的操作是便宜的内置操作。”)
我的感觉是,应该努力让你尽可能地把一流的对象归类,这对我来说意味着让他们理解尽可能多的操作符。
我已经有一段时间没有写函子了,但它看起来像这样:
class MyClass {....}
class LessThanMyClass : std:binary_function<MyClass, MyClass, bool>
{
public bool operator()(MyClass lhs, MyClass rhs)
{ return /* determine if lhs < rhs */ ; }
}
vector<MyClass> objs;
std::sort(objs.begin(), objs.end(), LessThanMyClass());
}
我可能不会像谷歌风格指南那样走得那么远。
我认为他们得到的是,当您重载operator<
and时operator==
,您正在为该类型的每次使用做出决定,而仿函数类型仅适用于该集合。
如果您唯一需要比较器的是将项目放入集合中,那么最好有一个专门用于该上下文的函数,而不是适用于所有上下文的运算符。
您可能希望按时间顺序对采购订单进行排序,但一般来说,按总价比较它们是有意义的。如果我们超载operator<
比较日期以便我们可以将它们加载到集合中,我们就会引入另一个客户可能滥用我们的风险,operator<
他们可能认为比较总价格。
我认为未定义 operator< 背后的信息是排序是集合的属性,而不是对象的属性。相同对象的不同集合可能有不同的排序。所以你应该在指定集合的类型时使用一个单独的函子而不是 operator<。
但在实践中,您的许多类可能具有自然排序,这是您应用程序中集合中使用的唯一排序。在其他情况下,排序甚至可能与应用程序无关,只是集合,以便以后可以找到项目。在这些情况下,定义 operator< 非常有意义。
请记住,当我们设计对象模型时,我们只是对现实世界的一个子集进行建模。在现实世界中,可能有无数种不同的方法来对同一类的对象进行排序,但在我们正在工作的应用程序领域中,可能存在一种相关的方法。
如果代码演变为需要与第一次一样相关的第二次排序,则应重构该类以删除 operator< 并将两个排名函数放在单独的函子中。这表明没有一个排名比其他排名更重要的意图。
关于算术运算符,除非您正在实现算术类型,否则不应重载它们。
当然,每条规则都有例外。如果你不知道你是否应该例外,你可能不应该。经验将是你的向导。
函子是一个带有operator ()
. 在这种情况下,该方法将采用被比较类型的两个参数,bool
如果第一个小于第二个,则返回结果。
编辑:以James Curran所说的为基础,您可以在类中定义您的仿函数。因此,例如:
class MyClass
{
struct LessThan : public std::binary_function<MyClass, MyClass, bool>
{
bool operator()(const MyClass & first, const MyClass & second) const
{
return first.key < second.key;
}
};
};
具有讽刺意味的是,仿函数还需要覆盖运算符(函数调用运算符 - operator ()
),所以我不确定它们的意义是什么。