你会在哪里使用友元函数而不是静态成员函数?

时间:2010-02-22 23:46:49

标签: c++ friend-function static-functions

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

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

例如,当实现创建只有私有构造函数的类foo实例的工厂时,该工厂函数应该是foo的静态成员(您可以调用foo::create() )或者它应该是朋友功能(你会打电话给create_foo())?

15 个答案:

答案 0 :(得分:67)

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

  1. 访问类
  2. 的内部
  3. 属于班级
  4. 的范围 必须在实例上调用
  5. friend只获得1。

    static个函数获得1和2。

答案 1 :(得分:39)

问题似乎解决了程序员需要引入一个在类的任何实例上工作的函数的情况(因此有可能选择{ {1}}成员函数)。因此,我将此答案限制在以下设计情境中,其中选择在静态函数static和朋友自由函数f()之间:

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 } f()语义(我稍后会再回过头来看),这个有限的场景有一个简单的答案: A函数更可取。我看到了两个原因。


通用算法

主要原因是可以编写如下模板:

static

如果我们在其接口上有两个或多个具有template<typename T> void g() { T::f(); } 函数static的类,这将允许我们编写一个函数,在任何此类上一般调用f()

如果我们使f()成为一个免费的非成员函数,则无法编写等效的泛型函数。虽然我们可以将f()置于命名空间中,以便可以使用f()语法来模仿N::f()语法,但仍然无法编写模板函数,例如如上面的A::f(),因为名称空间名称不是有效的模板参数。

冗余声明:

第二个原因是 if 我们将自由函数g<>()放在命名空间中,我们不允许直接在类定义中内联它的定义而不引入任何其他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()的声明。 1}}:如果不这样做会导致编译器抱怨A必须在名称f()合法使用之前必须在名称空间N内声明的事实。

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

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

N f()N的声明和定义无法加入(一般)的原因是f()应该访问A的内部,并且因此,在定义A时必须看到f()的定义。然而,如前所述,f()内的N声明必须在friend内的相应A声明之前看到。这有效地迫使我们将声明和f()的定义分开。


语义考虑因素:

虽然上述两点普遍有效,但有理由可能会将f()声称为static而不是friend A,反之亦然由话语世界驱动。

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

另一方面,friend函数,尽管被授予对其所熟悉的类的内部成员的访问权限,但仍然是逻辑上外部的算法班级的定义。

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

因此,在特定的应用程序域中,设计者在决定是否将前者设为friend或者a时,可能需要考虑函数和类的语义。后者的成员(这不仅适用于static函数,也适用于非static函数,其他语言约束可能会干预)。

该函数在逻辑上是否有助于表征类和/或其行为,还是外部算法?在不了解特定应用领域的情况下,无法回答这个问题。


<强> TASTE:

我认为,除了刚刚给出的其他论点完全源于品味:事实上,免费friendstatic成员方法都允许清楚地说明一个类的接口是一个单独的点(类的定义),所以在设计方面它们是等价的(当然,以上述观察为模)。

剩下的差异是风格的:我们是否要在声明函数时编写static关键字或friend关键字,以及我们是否要编写A::类范围限定符时定义类而不是N::命名空间范围限定符。因此,我不会对此作进一步评论。

答案 2 :(得分:10)

差异清楚地表达了类和函数之间关系的意图。

如果您想故意指出两个无关类之间或类和函数之间的强耦合和特殊关系,请使用friend

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

答案 3 :(得分:4)

当您需要对于类的每个实例都相同的函数时,将使用静态函数。这些函数无法访问“this”指针,因此无法访问任何非静态字段。当您需要可以在不实例化类的情况下使用的函数时,它们经常被使用。

朋友功能是不属于班级的功能,您希望授予他们访问您班级私人成员的权限。

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

答案 4 :(得分:4)

朋友功能(和类)可以​​访问您班级的私人和受保护成员。 使用友元函数或类很少有好的理由。一般都要避免使用它们。

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

  • 作为回调函数
  • 操纵类范围的成员
  • 检索您不想在头文件中枚举的常量数据
  • 答案 5 :(得分:2)

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

    答案 6 :(得分: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类的数据。

    答案 7 :(得分: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
      };
      

    答案 8 :(得分:1)

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

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

    答案 9 :(得分:1)

    静态函数可以继承友元函数。因此,当使用静态函数和友元函数实现目标时,请考虑是否要继承它。

    答案 10 :(得分:0)

    静态函数是无法访问this的函数。

    友元功能是一种可以访问该类私人成员的功能。

    答案 11 :(得分: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

    所描述的一样

    答案 12 :(得分:0)

    朋友功能可以访问其他类的私有和受保护成员。 意味着它们可用于访问私人或公共的所有数据天气。 因此,友元函数用于访问静态方法无法访问的数据。

    这些方法是静态的,被称为很多次,在每个对象中声明一个不同的位置,因为它们变得太昂贵(在内存方面)。 在示例的帮助下可以明确这一点: 让类的名称是事实,其数据成员是n(表示涉及因子的整数) 然后在这种情况下将find_factorial()声明为静态将是明智的决定!!

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

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

    使用好友功能时?使用静态功能时?

    现在如果两者都是解决问题的可行方案, 我们可以在可访问性(私有数据的可访问性)和内存效率方面加强其适用性。 默认情况下,没有人可以优先选择,因为当我们需要更好的内存管理时,有很多情况,有时我们会关注数据范围。

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

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

    我希望这会对你有所帮助!!

    答案 13 :(得分:-1)

    我认为这是:

    朋友功能 - 当您需要访问其他类成员时,但这些类不相关。静态函数 - 当你不需要访问'this'指针时。但是,我觉得还有更多......

    答案 14 :(得分:-2)

    1. 静态数据成员始终共享内存。
    2. 只有静态函数才能使用静态数据成员。
    3. 可以使用类名调用静态成员函数。
    4. 当我们在类中创建静态成员或成员函数的对象时,必须在类之外定义它们。它会自动初始化值。
    5. 它总是使用关键字static。
    6. 静态成员可以共享所有对象。
    7. 数据成员和成员函数的类型和范围在类之外。
    8. 必须在类之外定义静态成员变量。