这个问题似乎解决了程序员需要引入一个对类的任何实例都不起作用的函数的情况(因此有可能选择成员函数)。因此,我将这个答案限制在以下设计情况,在静态函数和无友元函数之间进行选择: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
}
在事先不知道 and 的语义的情况下(我稍后会回到这个),这个有限的场景有一个简单的答案:函数是可取的。我看到了两个原因。f()
A
static
通用算法:
主要原因是可以写出如下这样的模板:
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()
的声明,然后再进行内部的相应声明。这有效地迫使我们将声明和定义分开。N
friend
A
f()
语义考虑:
虽然上述两点是普遍有效的,但人们可能更喜欢将其声明f()
为static
而不是使其成为friend
或A
反之亦然,这是由话语领域驱动的。
为了澄清,重要的是要强调一个类的成员函数,无论它是static
还是非static
,在逻辑上都是该类的一部分。它有助于其定义,因此提供了它的概念表征。
另一方面,一个函数,尽管被授予访问它是其朋友的类的内部friend
成员的权限,但它仍然是一个逻辑上在类定义之外的算法。
一个函数可以friend
属于多个类,但它只能是一个类的成员。
因此,在特定的应用领域中,设计者可能希望在决定是使前者成为一个还是后者的成员时同时考虑函数和类的语义(这不仅适用于函数,也适用于非-功能以及其他语言限制可能干预的地方)。friend
static
static
该函数是否在逻辑上有助于描述一个类和/或其行为,或者它是一个外部算法?如果不了解特定的应用领域,就无法回答这个问题。
品尝:
我相信除了刚才给出的任何论点之外的任何论点都纯粹出于品味问题:事实上,自由friend
和static
成员方法都允许清楚地说明一个类的接口是什么到一个单一的位置(类的定义),所以在设计方面它们是等价的(当然,以上述观察为模)。
其余的区别是风格上的:我们是要在声明函数时写static
关键字还是friend
关键字,以及在定义类时是否要写A::
类作用域限定符而不是N::
命名空间作用域限定符。因此,我将不再对此发表评论。