这个问题似乎涉及到程序员需要引入一个不适用于任何类的实例的函数(因此可以选择静态成员函数)。因此,我将限制本答案的范围为以下设计情况,即在静态函数f()
和友元自由函数f()
之间进行选择:
struct A
{
static void f();
private:
friend void f();
static int x;
};
int A::x = 0;
void A::f()
{
cout << x;
}
void f()
{
cout << A::x;
}
int main()
{
A::f();
f();
}
在不了解f()
和A
的语义(稍后我会回来讨论这个问题)的情况下,这种局限性场景有一个简单的答案:static
函数更好。我认为有两个原因。
通用算法:
主要原因是可以编写以下模板:
template<typename T> void g() { T::f(); }
如果我们有两个或更多的类,在它们的接口中都有一个静态函数
f()
,那么这将允许我们编写一个单一的函数,以通用方式在任何这样的类上调用
f()
。
如果我们将
f()
作为自由的非成员函数,则无法编写等效的通用函数。虽然我们可以将
f()
放入命名空间中,以便使用
N::f()
语法来模仿
A::f()
语法,但仍然不可能编写如上所示的
g<>()
模板函数,因为命名空间名称不是有效的模板参数。
第二个原因是,如果我们将自由函数
f()
放入命名空间中,就不能直接在类定义中内联其定义,而不引入任何其他声明
f()
的声明。
struct A
{
static void f() { cout << x; }
private:
friend void N::f() { cout << x; }
static int x;
};
为了解决上述问题,我们需要在类
A
的定义之前加上以下声明:
namespace N
{
void f();
}
struct A
{
...
private:
friend void N::f() { cout << x; }
...
};
然而,这样做违背了我们只在一个地方声明和定义
f()
的意图。
此外,如果我们想要分别声明和定义
f()
,同时保持
f()
在命名空间中,我们仍然需要在
A
类定义之前引入
f()
的声明:否则编译器会抱怨
f()
必须在命名空间
N
内声明才能合法使用
N::f
名称。
因此,现在我们将有
f()
在三个不同的地方提到而不是两个(声明和定义):
- 在
A
定义之前,在命名空间
N
内声明;
- 在
A
定义内部的
friend
声明;
- 在命名空间
N
内定义
f()
。
一般来说,
f()
的声明和定义不能合并的原因是
f()
应该访问
A
的内部,因此,在定义
f()
时必须看到
A
的定义。然而,正如先前所述,在进行相应的
friend
声明之前,必须看到
N
内部的
f()
声明。这实际上强制我们分离
f()
的声明和定义。
语义上考虑:
虽然上述两点是普遍有效的,但有些原因会使人们更喜欢将
f()
声明为
static
而不是将其作为
A
的
friend
或反之,这是由话题的范围驱动的。
需要澄清的是,一个类的成员函数,无论它是静态的还是非静态的,逻辑上都是该类的一部分。它对其定义做出了贡献,从而提供了概念上的特征。
另一方面,尽管被授予访问其友元类内部成员的权限,但
friend
函数仍然是逻辑上外部于类定义的算法。
一个函数可以是多个类的友元,但只能是一个类的成员。
因此,在特定的应用领域中,设计师在决定将函数作为友元或成为后者的成员时,可能希望考虑到两者的语义(这不仅适用于静态函数,还适用于非静态函数,其中可能会涉及其他语言约束)。
函数在逻辑上是否有助于表征类及/或其行为,还是它更像是一个外部算法?这个问题需要了解特定的应用领域才能回答。
品味:
我认为除了刚才给出的理由之外,任何其他争论都纯粹源自于
品味:实际上,自由的友元和静态成员方法都允许清楚地声明类的接口位于单一位置(类的定义),因此从设计角度来看,它们是等效的(当然,要考虑上述观察结果)。
剩下的区别是风格上的:我们是否想在声明函数时写入
static
关键字或
friend
关键字,以及我们是否想在定义类时写入
A::
类作用域限定符而非
N::
命名空间作用域限定符。因此,我不会再进一步评论这个问题。