8

在 Bjarne Stroustrup 所著的 The C++ Programming Language 一书中,作者介绍了一个必须实现函数 inv() 的类 Matrix。在第 11.5.1 节中,他谈到了这样做的两种可能性。一种是创建成员函数,另一种是创建友元函数 inv()。然后在第 11.5.2 节的末尾,他谈到选择是使用朋友还是成员函数,他说:

如果 inv() 确实反转了 Matrix m 本身,而不是返回一个与 m 倒数的新 Matrix,它应该是一个成员。

为什么会这样?友元函数不能改变矩阵的状态并返回对该矩阵的引用吗?是不是因为我们调用函数时可能会传递一个临时矩阵?...

4

3 回答 3

13

老实说,我认为做出这样决定的唯一原因是语法便利和传统。我将通过展示两者之间的差异以及这些差异在做出决定时的重要性来解释原因。

非会员朋友功能和公共会员功能有什么区别?不多。毕竟,成员函数只是一个带有隐藏this参数并且可以访问类的私有成员的常规函数​​。

// what is the difference between the two inv functions?
// --- our code ---
struct matrix1x1 { // this one is simple :P
private:
    double x;
public:
    //... blah blah
    void inv() { x = 1/x; }
    friend void inv(matrix1x1& self) { self.x = 1/self.x; }
};
matrix1x1 a;

// --- client code ---

// pretty much just this:
a.inv();
// vs this:
inv(a);

void lets_try_to_break_encapsulation(matrix1x1& thingy) {
    thingy.x = 42; // oops, error. Nope, still encapsulated.
}

它们都提供相同的功能,并且绝不会改变其他功能的功能。相同的内部结构暴露于外部世界:在封装方面没有区别。其他函数绝对没有什么不同的,因为有一个友元函数可以修改私有状态。

实际上,可以将大多数具有大多数函数的类编写为非成员友元函数(虚函数和一些重载的运算符必须是成员),提供完全相同的封装量:用户不能在不修改类的情况下编写任何其他友元函数,并且没有朋友功能以外的功能可以访问私有成员。我们为什么不这样做?因为这违背了 99.99% 的 C++ 程序员的风格,并且没有什么太大的优势可以从中获得。

不同之处在于函数的性质和调用方式。作为成员函数意味着您可以从中获取指向成员函数的指针,而作为非成员函数意味着您可以获取指向它的函数指针。但这很少相关(尤其是像std::function周围这样的通用函数包装器)。

剩下的区别是句法。D 语言的设计者决定统一整个事情,并说您可以通过传递一个类似的对象直接调用成员函数inv(a),并调用一个自由函数作为其第一个参数的成员,例如a.inv()。并且没有任何类会因为这个或其他原因而突然被严重封装。1

要解决问题中的特定示例,应该inv是成员还是非成员?对于我上面概述的熟悉性论点,我可能会使其成为成员。非风格上,它没有任何区别。


1 . 这在 C++ 中不太可能发生,因为在这一点上这将是一个突破性的变化,没有实质性的好处。举一个极端的例子,它会破坏matrix1x1我上面写的类,因为它使两个调用都模棱两可。

于 2012-10-10T06:10:33.033 回答
0

OOD 固有的封装理念(C++ 试图推广)规定对象状态只能从内部修改。它在语法上是正确的(编译器允许这样做),但应该避免。

在不使用预定义接口的情况下让整个系统中的元素相互更改是容易出错的。对象存储和功能可能会发生变化,四处寻找使用对象特定部分的代码(可能很大)将是一场噩梦。

于 2012-10-10T05:24:32.453 回答
0

关于使用朋友有两种相反的论点:

一方面说朋友减少了封装,因为现在您让外部实体访问类的内部,而内部只能由成员方法修改。

另一方说朋友实际上可以增加封装,因为您可以将类的内部访问权限授予一小组外部实体,从而无需将内部类属性公开给所有人,以便这些外部实体可以访问/操作他们。

双方都可以争论,但我倾向于同意第一种选择。

至于您的问题,正如 PherricOxide 在他的评论中提到的那样:如果需要修改类的内部属性,最好通过成员方法完成,从而强制封装。这与上面提到的第一个选项是一致的。

于 2012-10-10T05:33:54.970 回答