为什么可以在类定义中放置友元函数定义?

35

朋友函数岂不应该显式地在类外定义吗?
如果是这样,那么为什么我可以像任何成员函数一样在类定义内声明朋友函数?
这是什么?
只适用于某些运算符(如 < 运算符),还是适用于所有运算符?
如果对所有运算符都适用,这样做有什么缺点吗?
应该避免使用吗?如果是,为什么?

class person 
{
public:
    bool operator<(int num)
    {
        return  x < num ? true : false ;
    }
    bool operator<(person& p)
    {
        return  x < p.x ? true : false ;
    }

    friend bool operator<(int num, person &p)
    {
        return  p.x < num ? true : false ;
    }

    void setX(int num)
    {
        x = num;
    }

private:
    int x;


};
更新
我不是在问选择非成员运算符重载还是成员运算符重载。
我想知道的是:
为什么我们被允许将友元方法的定义移动到类定义内部
这不是违反了任何东西吗?如果不是,那么我们为什么需要友元?
我们可以简单地将重载定义为成员函数(我知道成员函数的限制)。但是,我想问的是,为什么编译器不会抱怨我没有在类定义外定义友元函数,因为它不需要在其中(因为它有类参数)。 那么,为什么我们允许在类定义内部定义友元函数?

如果您询问的是“friend”函数的定义,则应更改问题的标题以反映这一点。 - juanchopanza
那我应该写什么呢? - Hossein
你可以这样做,因为标准明确允许。至于标准允许的原因——我猜想是因为它被认为足够方便而值得这样做。 - Vaughn Cato
1
你可以问:“为什么可以在类定义内部放置友元函数的定义?” - juanchopanza
5个回答

13

朋友函数不是应该在类外部明确定义吗?

朋友函数可以在类声明内定义(给定函数体)。这些函数是内联函数,就像成员内联函数一样,它们的行为好像它们是在看到所有类成员之后但在类范围关闭之前(类声明结束)立即定义的一样。在类声明内定义的友元函数处于封闭类的作用域内。 引用

只有某些运算符如小于号可以使用朋友函数吗?还是所有运算符都适用?

最好尝试避免使用朋友函数,因为它们与您试图使用私有类作用域相反,并且主要“隐藏”变量。如果您的所有函数都是朋友函数,那么拥有私有变量有什么用呢?

尽管如此,有一些常见的运算符通常被声明为友元函数,其中包括 operator<<operator>>


非常感谢:) 我并不试图将我所需要的所有函数都声明为友元,我很好奇一个友元函数(仅用于操作重载而不做其他事情)为什么可以在类内定义,以及为什么被允许。 - Hossein
@Hossein,我给你的引文实际上并没有说“为什么被允许”,而是解释了为什么“不被禁止”。 - Alexandru Barbarosie
我知道,但是“友元函数可以在类声明内定义”这部分说我们被允许这样做,我理解友元函数并不是万能的答案,正如你也明确提到的那样。当我说这个时,我指的是引用的部分,它在某种程度上澄清了我的问题。 - Hossein
它们是“文件作用域”吗?它们不应该被插入到周围的作用域中,无论是文件、命名空间、函数还是类吗? - Felix Dombek
1
在类声明内定义的友元函数处于封闭类的作用域中。不确定这部分是否准确。至少我无法发现在类声明内部和外部定义的友元函数之间有任何区别 - 两者都作为自由函数而没有额外的作用域限制。 - The Dreams Wind
@TheDreamsWind 说得好。实际上,两者都有点道理...关键在于ADL。如果友元函数有一个封闭它的类类型的参数,那么在类外的非限定调用点将解析出正确的作用域。另一方面,如果没有(例如friend void f() {}),那么编译将失败,f将被声明为未定义。 - undefined

6

Alexandru Barbarosie的回答是正确的。这意味着我们可以在类中声明一个非成员函数的友元函数。这对于组织代码非常有用。如果不清楚,我认为一个例子可以帮助理解。

#include <iostream>

class A {
    public:
        A(int val) : val(val) {}
        // The following isn't a member function, it is a friend 
        // function declared inside the class and it has file scope
        friend void draw (A &a) {
            std::cout << "val: " << a.val << "\n";
        }
    private:
        int val;
};

int main() {
    A a(5);
    draw(a); // outputs "val: 5"
    //A::draw(a); // Error: 'draw' is not a member of 'A'
}

4

由于运算符需要了解使用的表达式右侧的详细信息,如果它必须访问该侧上驻留的类型的私有数据,则需要与该类成为 friend

如果您正在尝试将一个intperson进行比较,就像您的示例一样,有两种选择:

  • 提供从personint的隐式转换,以便<可以在不访问任何私有字段的情况下使用它。
  • 或者将运算符声明为personfriend,以便它可以访问比较右侧的x

3
如果只有友元声明在类定义内部,而友元函数定义在外部,仍然是可能的。 - Nicola Musatti
谢谢,但我不知道为什么我们要使用友元函数,我问的是为什么我们被允许将友元函数的定义移动到类定义内部。这不违反任何规定吗?如果不是,那么我们为什么需要友元函数呢?我们可以简单地将重载定义为成员函数(我知道成员函数的限制),但是我想知道为什么编译器没有抱怨我没有在类定义外定义友元函数,因为它不需要在类定义内部(因为它有类参数)。 - Hossein
我建议你在这里看一下:https://dev59.com/62855IYBdhLWcg3wUCWC#4421729 - Jack
@Hossein 显然这不会“违反任何东西”。否则你甚至无法提出问题,因为它无法编译。 - juanchopanza
违反指的是任何违背良好实践或类似规定的行为。 - Hossein

4

如果你正在创建一个仅包含头文件的类(这使部署变得更加容易),那么在类中定义友元函数是唯一的方法,因为定义只能出现在单个翻译单元中。常规的防卫技术不起作用,因为这只处理递归包含等事项。

如果你试图编写符合标准的代码,这可能是一个大问题。例如,要实现C++规范标准中的RandomNumberEngine命名要求,需要提供operator<<。这必须是一个friend,以将std::ostream&对象作为其第一个参数(否则它将看起来像一个普通的、单参数成员函数操作符重载)。通常,friend声明将放在类定义中,函数定义将放在单独的.cpp源文件中。但是,如果你想要一个仅包含头文件的实现,它必须在类中定义,以避免多重定义错误。


4
“在类内定义友元函数是唯一可行的方法”,也可以使用inline函数。 - HolyBlackCat
1
@HolyBlackCat,确实如此,但是我不喜欢这样做,因为具有非静态链接的内联函数在翻译单元之间不需要相同,这只会引起麻烦。对于一个操作符来说,将定义与其类尽可能紧密地关联起来是有意义的。 - David G

2

正如Jack所提到的,友元函数在需要访问私有数据的地方是必需的。还有另一个目的,这与继承类型有关。只有派生类和其友元函数才能将指向私有基类的指针转换为派生类型。因此,您可能有时希望将某些函数设置为派生类的友元函数,以允许在函数体内进行此操作。


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