74

当我们希望非成员函数访问该类的私有成员时,我们将其设为类的朋友。这赋予它与静态成员函数相同的访问权限。这两种选择都会为您提供一个与该类的任何实例都不相关的函数。

我们什么时候必须使用朋友功能?我们什么时候必须使用静态函数?如果两者都是解决问题的可行选择,我们如何衡量它们的适用性?默认情况下应该首选一个吗?

例如,当实现一个创建foo只有一个私有构造函数的类实例的工厂时,该工厂函数应该是foo(你会调用foo::create())的静态成员还是应该是友元函数(你会调用create_foo())?

4

15 回答 15

80

Bjarne Stroustrup 的第 11.5 节“C++ 编程语言”指出普通成员函数有 3 件事:

  1. 访问类的内部
  2. 在类的范围内
  3. 必须在实例上调用

friends 只得到 1。

static函数得到 1 和 2。

于 2010-02-23T00:08:38.050 回答
47

这个问题似乎解决了程序员需要引入一个对类的任何实例都不起作用的函数的情况(因此有可能选择成员函数)。因此,我将这个答案限制在以下设计情况,在静态函数和无友元函数之间进行选择:staticf()f()

struct A
{
    static void f();     // Better this...
private:
    friend void f();  // ...or this?
    static int x;
};

int A::x = 0;

void A::f() // Defines static function
{
    cout << x;
}

void f() // Defines friend free function
{
    cout << A::x;
}

int main()
{
    A::f(); // Invokes static function
    f();    // Invokes friend free function
}

在事先不知道 and 的语义情况下(我稍后会回到这个),这个有限的场景有一个简单的答案:函数是可取的。我看到了两个原因。f()Astatic


通用算法:

主要原因是可以写出如下这样的模板:

template<typename T> void g() { T::f(); }

如果我们有两个或多个类在其接口上有一个static函数,这将允许我们编写一个在任何此类类上通用f()调用的单个函数。f()

f()如果我们创建一个自由的非成员函数,就没有办法编写一个等价的泛型函数。虽然我们确实可以放入f()命名空间,这样N::f()语法就可以用来模仿A::f()语法,但仍然不可能编写像g<>()上面这样的模板函数,因为命名空间名称不是有效的模板参数。

冗余声明:

第二个原因是,如果我们将 free 函数f()放在命名空间中,我们将不允许在不引入任何其他声明的情况下将其定义直接内联到类定义中f()

struct A
{
    static void f() { cout << x; } // OK
private:
    friend void N::f() { cout << x; } // ERROR 
    static int x;
};

为了解决上述问题,我们将在类的定义之前A添加以下声明:

namespace N
{
    void f(); // Declaration of f() inside namespace N
}

struct A
{
    ...
private:
    friend void N::f() { cout << x; } // OK
    ...
};

然而,这违背了我们f()在一个地方声明和定义的意图。

此外,如果我们想在命名空间中f()分别声明和定义f(),我们仍然必须f()在类定义之前引入声明 for A:如果不这样做,编译器会抱怨f()必须在内部声明的事实名称N之前的命名空间N::f可以合法使用。

因此,我们现在将在三个f()不同的地方提到,而不仅仅是两个(声明和定义):

  • N定义之前命名空间内的声明A
  • 定义中的friend声明A
  • f()内部命名空间的定义N

f()inside的声明和定义N不能连接的原因(通常)是f()应该访问的内部A,因此,A必须在定义时看到f()的定义。然而,如前所述,必须先看到内部f()的声明,然后再进行内部的相应声明。这有效地迫使我们将声明和定义分开。NfriendAf()


语义考虑:

虽然上述两点是普遍有效的,但人们可能更喜欢将其声明f()static而不是使其成为friendA反之亦然,这是由话语领域驱动的。

为了澄清,重要的是要强调一个类的成员函数,无论它是static还是非static,在逻辑上都是该类的一部分。它有助于其定义,因此提供了它的概念表征。

另一方面,一个函数,尽管被授予访问它是其朋友的类的内部friend成员的权限,但它仍然是一个逻辑上在类定义之外的算法。

一个函数可以friend属于多个类,但它只能是一个类的成员

因此,在特定的应用领域中,设计者可能希望在决定是使前者成为一个还是后者的成员时同时考虑函数和类的语义(这不仅适用于函数,也适用于非-功能以及其他语言限制可能干预的地方)。friendstaticstatic

该函数是否在逻辑上有助于描述一个类和/或其行为,或者它是一个外部算法?如果不了解特定的应用领域,就无法回答这个问题。


品尝:

我相信除了刚才给出的任何论点之外的任何论点都纯粹出于品味问题:事实上,自由friendstatic成员方法都允许清楚地说明一个类的接口是什么到一个单一的位置(类的定义),所以在设计方面它们是等价的(当然,以上述观察为模)。

其余的区别是风格上的:我们是要在声明函数时写static关键字还是friend关键字,以及在定义类时是否要写A::类作用域限定符而不是N::命名空间作用域限定符。因此,我将不再对此发表评论。

于 2013-02-14T14:05:56.140 回答
13

区别在于清楚地表达了类和函数之间关系的意图。

当您friend想有意指示两个不相关的类之间或类和函数之间的强耦合和特殊关系时使用。

static当函数在逻辑上是它所属的类的一部分时,可以使用成员函数。

于 2013-02-11T15:11:10.213 回答
6

Friend 函数(和类)可以​​访问类的私有成员和受保护成员。很少有使用友元函数或类的好案例。一般避免使用它们。

静态函数只能访问静态数据(即类范围的数据)。可以在不创建类的实例的情况下调用它们。静态函数非常适合您希望类的所有实例都以相同方式运行的情况。您可以使用它们:

  • 作为回调函数
  • 操作类范围的成员
  • 检索您不想在头文件中枚举的常量数据
  • 于 2010-02-23T00:03:33.813 回答
    5

    当您想要一个对类的每个实例都相同的函数时,可以使用静态函数。此类函数无法访问“this”指针,因此无法访问任何非静态字段。当您想要一个无需实例化类即可使用的函数时,通常会使用它们。

    朋友函数是不在类中的函数,您希望让它们访问您的类的私有成员。

    这(静态与朋友)不是使用一个与另一个的问题,因为它们不是对立的。

    于 2010-02-22T23:55:36.880 回答
    2

    该标准要求 operator = () [] 和 -> 必须是成员,并且特定于类的
    运算符 new、new[]、delete 和 delete[] 必须是静态成员。如果
    出现我们不需要类的对象来调用函数的情况,则将函数设为
    静态。对于所有其他函数:
    如果一个函数需要操作符 = () [] 和 -> 用于流 I/O,
    或者如果它需要对其最左边的参数进行类型转换,或者如果它可以单独使用类的公共接口来实现,
    如果它需要虚拟行为,则使其成为非成员(如果需要在前两种情况下为朋友) ,则
    添加一个虚拟成员函数以提供虚拟行为
    并根据
    else实现
    使其成为会员。

    于 2010-02-23T00:09:43.970 回答
    2
    • 喜欢朋友而不是静态成员的一个原因是当函数需要用汇编(或其他语言)编写时。

      例如,我们总是可以在 .cpp 文件中声明一个 extern "C" 友元函数

      class Thread;
      extern "C" int ContextSwitch(Thread & a, Thread & b);
      
      class Thread
      {
      public:
          friend int ContextSwitch(Thread & a, Thread & b);
          static int StContextSwitch(Thread & a, Thread & b);
      };
      

      后来在汇编中定义:

                      .global ContextSwitch
      
      ContextSwitch:  // ...
                      retq
      

      从技术上讲,我们可以使用静态成员函数来执行此操作,但由于名称修改( http://en.wikipedia.org/wiki/Name_mangling),在程序集中定义它并不容易

    • 另一种情况是当您需要重载运算符时。重载运算符只能通过朋友或非静态成员来完成。如果操作符的第一个参数不是同一个类的实例,那么非静态成员也不起作用;朋友将是唯一的选择:

      class Matrix
      {
          friend Matrix operator * (double scaleFactor, Matrix & m);
          // We can't use static member or non-static member to do this
      };
      
    于 2013-02-11T15:02:43.423 回答
    2

    静态函数只能访问一个类的成员。Friend 函数可以访问多个类,如下代码所示:

    class B;
    class A { int a; friend void f(A &a, B &b); };
    class B { int b; friend void f(A &a, B &b); };
    void f(A &a, B &b) { std::cout << a.a << b.b; }
    

    f() 可以访问 A 类和 B 类的数据。

    于 2013-02-09T20:26:09.143 回答
    1

    静态函数是无权访问的函数this

    友元函数是可以访问类的私有成员的函数。

    于 2010-02-22T23:54:18.097 回答
    1

    如果函数不需要读取或修改类的特定实例的状态(这意味着您不需要修改内存中的对象),或者如果您需要使用函数指针指向类的成员函数。在第二个实例中,如果您需要修改常驻对象的状态,则需要传入this并使用本地副本。在第一种情况下,执行特定任务的逻辑不依赖于对象的状态,但您的逻辑分组和封装将使其成为特定类的成员时,可能会发生这种情况。

    当您创建的代码不是您的类的成员并且不应该是您的类的成员时,您使用友元函数或类,但具有规避私有/受保护封装机制的合法目的。这样做的一个目的可能是您有两个需要一些公共数据的类,但对逻辑进行两次编码会很糟糕。真的,我只在我编写过的类的 1% 中使用了这个功能。很少需要它。

    于 2010-02-22T23:59:12.690 回答
    1

    友元函数不能被继承,而静态函数可以。所以当一个目标可以同时使用静态函数和友元函数时,考虑是否要继承它。

    于 2016-10-17T12:52:01.073 回答
    0

    友元函数可以访问其他类的私有成员和受保护成员。意味着它们可用于访问私有或公共的所有数据。所以友元函数用于访问静态方法不能访问的数据。

    这些方法是静态的,它们被调用了很多次,以至于在每个对象中声明不同的位置,因为它们变得太昂贵(就内存而言)。这可以在示例的帮助下明确:假设类的名称是 fact 并且它的数据成员是 n(它表示涉及阶乘的整数),那么在这种情况下,将 find_factorial() 声明为 static 将是明智的决定!

    它们用作回调函数来操作类范围的成员以检索您不想在头文件中枚举的常量数据

    现在我们清楚了以下问题..

    什么时候使用好友功能?什么时候使用静态函数?

    现在,如果两者都是解决问题的可行选项,我们可以在可访问性(私有数据的可访问性)和内存效率方面权衡它们的适用性。默认情况下,没有人可以首选,因为在很多情况下我们需要更好的内存管理,有时我们关心数据的范围。

    例如:当我们必须在每个小时间实例之后调用 create() 方法并且我们对数据范围不感兴趣时​​, foo::create() 将优于 create_foo()

    如果我们有兴趣获取多个类的私有信息,那么 create_foo() 将优先于 foo::create()。

    我希望这会帮助你!

    于 2013-02-14T23:31:15.633 回答
    0

    静态函数可以以许多不同的方式使用。

    例如作为简单的工厂函数:

      class Abstract {
      private:
        // no explicit construction allowed
        Abstract(); 
        ~Abstract();
    
       public:
         static Abstract* Construct() { return new Abstract; }
         static void Destroy(Abstract* a) { delete a; }
       };
    
       ...
       A* a_instance = A::Conctruct();
       ...
       A::Destroy(a_instance);
    

    这是一个非常简单的例子,但我希望它能解释我的意思。

    或者作为线程函数与你的类一起工作:

     class A {
    
     public:
        static void worker(void* p) {
                A* a = dynamic_cast<A*>(p);
                do something wit a;
        }   
     } 
    
     A a_instance;
     pthread_start(&thread_id, &A::worker, &a_instance);
     .... 
    

    朋友是完全不同的故事,他们的用法与thebretness所描述的完全一样

    于 2010-02-23T00:08:03.187 回答
    -1

    这是我认为的:

    朋友功能 - 当您需要访问不同的类成员,但类不相关时。静态函数 - 当您不需要访问“this”指针时。但是,我觉得还有更多的东西......

    于 2010-02-22T23:55:59.163 回答
    -2
    1. 静态数据成员总是共享内存。
    2. 只有静态函数可以使用静态数据成员。
    3. 静态成员函数可以用类名调用。
    4. 当我们在类中创建静态成员或成员函数的对象时,它们必须在类之外定义。它将自动初始化该值。
    5. 它总是使用关键字静态。
    6. 静态成员可以被所有对象共享。
    7. 数据成员和成员函数的类型和范围在类之外。
    8. 静态成员变量必须在类之外定义。
    于 2016-10-12T01:41:41.100 回答