哪些运算符应该声明为友元?

43

在一些书籍中以及互联网上,我看到过像“operator==应该声明为友元”的建议。

当操作符必须被声明为友元时,应如何理解?什么情况下应将其声明为成员?除了==<<之外,哪些运算符最常需要声明为友元?


需要访问私有类成员的非成员函数需要成为友元函数。 - AJG85
4
@AJG85认为问题是关于何时选择成员,何时选择非成员(在必要时将其加为好友)。 - Mike Seymour
1
这有点主观,但我遵循Scott Meyers的建议,并尽可能优先选择非成员非友元函数。在大多数情况下,我会更喜欢成员函数而不是友元函数。友元函数最好的情况可能是在您想要具有左侧隐式提升的公共接口的情况下。 - AJG85
4个回答

56
这取决于一个类是否在调用operator==(或其他运算符)的左侧或右侧。如果一个类将出现在表达式的右侧,并且没有提供可与左侧比较的类型的隐式转换,则需要将operator==实现为单独的函数或该类的friend。如果该运算符需要访问私有类数据,则必须声明为friend
例如:
class Message {
    std::string content;
public:
    Message(const std::string& str);
    bool operator==(const std::string& rhs) const;
};

允许您将消息与字符串进行比较

Message message("Test");
std::string msg("Test");
if (message == msg) {
    // do stuff...
}

但是不能反过来。
    if (msg == message) { // this won't compile

你需要在类内声明一个友元operator==

class Message {
    std::string content;
public:
    Message(const std::string& str);
    bool operator==(const std::string& rhs) const;
    friend bool operator==(const std::string& lhs, const Message& rhs);
};

或者声明到适当类型的隐式转换运算符

class Message {
    std::string content;
public:
    Message(const std::string& str);
    bool operator==(const std::string& rhs) const;
    operator std::string() const;
};

或者声明一个单独的函数,如果它不访问私有类数据,则不需要是友元函数。
bool operator==(const std::string& lhs, const Message& rhs);

4
回答不错,但您不需要在类定义之外声明友元函数。在类内部单个 friend 声明就足够了(如果您愿意,可以使用内联定义)。 - Mike Seymour
@Mike Huh,你说得对。出于某种原因,我没有想到那是有效的语法... - Chris Frederick

22

如果你把运算符放在类外,两个参数都可以参与隐式类型转换(而如果把运算符定义在类体内,则只有右操作数可以)。总的来说,这对于所有经典二元运算符(如 ==,!=, +, -, << 等)都是有益的。

当然,只有当你需要使用运算符对类进行友元声明时才应该这样做,而不是仅基于类的公共成员计算结果。


9
通常,只有实现为需要访问其操作的类的私有或受保护数据的自由函数的操作符应该声明为友元,否则它们应该只是非友元非成员函数。
通常,我作为成员函数实现的唯一操作符是那些基本不对称且操作数没有等价角色的操作符。我倾向于将其作为成员实现所需的操作符:简单赋值、()[]->以及复合赋值运算符、一元运算符和可能是流或类似流类的某些重载的<<>>。我从不重载&&||,
我倾向于将所有其他操作符作为自由函数实现,最好使用它们操作的类的公共接口,仅在必要时才退回到友元。
将诸如!===<+/等操作符作为非成员函数可以使左右操作数在隐式转换序列方面得到相同的处理,有助于减少惊人的不对称性数量。

7
没有人提到“隐藏的友元惯用语”,我怀疑这就是书中所说的内容。
长篇版本:https://www.justsoftwaresolutions.co.uk/cplusplus/hidden-friends.html 简短版本:
大多数情况下,通过ADL(参数依赖查找)找到运算符。这就是当你不在命名空间std中时,如何找到在std中定义的std::string的operator==。
其中一个问题,对于运算符来说很常见,就是巨大的重载集合。如果你尝试为某些不可打印的内容使用operator<<,你经常会在错误消息中看到这个问题。
因此,如果你在包含类的命名空间中直接声明operator==,它将工作,但它也将参与该命名空间中所有重载解析,这将减慢编译速度并给你更多的错误噪音。
介绍隐藏的友元:
struct X {
  friend bool operator==(const X& x, const X& y) {...}
};

如果操作数之一的类型为X,则只会考虑重载此operator==。在其他情况下,它将不会被看到,因此您的编译速度将更快,错误消息也更好。

对于所有两个操作数的运算符,例如operator<<以及其他旨在进行ADL的函数,如swap,情况也是如此。

我总是像这样定义我的运算符,现在被相当多的人认为是一个好习惯。

唯一的缺点是-没有很好的方法来定义它。您可能希望将其分派给某些私有函数。或者您可以这样做:https://godbolt.org/z/hMarb4 -但这意味着至少在一个cpp文件中,operator==将参与正常查找。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接