我一直在阅读c++常见问题,对朋友声明很好奇。我个人从未使用过它,但我对探索这门语言很感兴趣。

使用friend的一个好例子是什么?


读了一些常见问题,我喜欢<< >>操作符重载和添加作为这些类的朋友的想法。然而,我不确定这如何不会破坏封装。什么时候这些异常可以保持在OOP的严格范围内?


当前回答

编辑:阅读faq有点长,我喜欢<< >>操作符重载和添加作为这些类的朋友的想法,但我不确定这如何不打破封装

它将如何破坏封装?

当允许对数据成员进行不受限制的访问时,就打破了封装。考虑以下类:

class c1 {
public:
  int x;
};

class c2 {
public:
  int foo();
private:
  int x;
};

class c3 {
  friend int foo();
private:
  int x;
};

C1显然没有封装。任何人都可以读取和修改其中的x。我们没有办法实施任何形式的访问控制。

C2明显被封装了。没有对x的公共访问。你所能做的就是调用foo函数,它对类执行一些有意义的操作。

c3吗?它的封装程度低吗?它是否允许无限制地访问x?它是否允许未知函数访问?

不。它只允许一个函数访问类的私有成员。就像c2一样。就像c2一样,有访问权的函数不是“某个随机的未知函数”,而是“类定义中列出的函数”。就像c2一样,通过查看类定义,我们可以看到拥有访问权限的完整列表。

那么,这到底是如何减少封装的呢?同样数量的代码可以访问类的私有成员。类定义中列出了所有具有访问权限的人。

Friend不会破坏封装。这让一些Java程序员感到不舒服,因为当他们说“面向对象”时,他们实际上指的是“Java”。当他们说“封装”时,他们并不是说“必须保护私有成员不受任意访问”,而是说“在Java类中,唯一能够访问私有成员的函数是类成员”,尽管出于几个原因,这完全是无稽之谈。

首先,如上所述,它限制太大。没有理由不允许朋友方法做同样的事情。

第二,限制不够。考虑第四个类:

class c4 {
public:
  int getx();
  void setx(int x);
private:
  int x;
};

根据上述Java思想,这是完美封装的。 但是,它允许任何人读取和修改x,这有什么意义呢?(提示:事实并非如此)

底线: 封装是关于能够控制哪些函数可以访问私有成员。这与这些函数的定义具体位于何处无关。

其他回答

看来我迟到了14年。但情况是这样的。

TLDR TLDR

有了友类,您就可以将封装扩展到组成数据结构的类组。

TLDR

Your data structure in general consists of multiple classes. Similarly to a traditional class (supported by your programming language), your data structure is a generalized class which also has data and invariants on that data which spans across objects of multiple classes. Encapsulation protects those invariants against accidental modification of the data from the outside, so that the data-structure's operations ("member functions") work correctly. Friend classes extend encapsulation from classes to your generalized class.

太长

类是一种数据类型,它带有指定数据类型值子集(称为有效状态)的不变量。对象是类的有效状态。类的成员函数将给定对象从有效状态移动到另一个有效状态。

对象数据不能从类成员函数外部修改,这一点很重要,因为这可能会破坏类不变量(即将对象移动到无效状态)。封装禁止从类外部访问对象数据。这是编程语言的一个重要安全特性,因为它使无意中破坏类不变量变得很困难。

类通常是实现数据结构的自然选择,因为数据结构的属性(例如性能)依赖于其数据上的不变量(例如红黑树不变量)。然而,有时单个类不足以描述一个数据结构。

数据结构是将数据从有效状态移动到另一有效状态的数据、不变量和函数的任何集合。这是一个类的泛化。细微的区别在于,数据可能分散在不同的数据类型上,而不是集中在单一的数据类型上。

数据结构示例

A prototypical example of a data structure is a graph which is stored using separate objects for vertices (class Vertex), edges (class Edge), and the graph (class Graph). These classes do not make sense independently. The Graph class creates Vertexs and Edges by its member functions (e.g. graph.addVertex() and graph.addEdge(aVertex, bVertex)) and returns pointers (or similar) to them. Vertexs and Edges are similarly destroyed by their owning Graph (e.g. graph.removeVertex(vertex) and graph.removeEdge(edge)). The collection of Vertex objects, Edge objects and the Graph object together encode a mathematical graph. In this example the intention is that Vertex/Edge objects are not shared between Graph objects (other design choices are also possible).

一个Graph对象可以存储所有顶点和边的列表,而每个顶点可以存储一个指向其所属Graph的指针。因此,Graph对象表示整个数学图,只要需要数学图,就可以传递它。

不变的例子

图数据结构的一个不变量是顶点被列在它的所有者图的列表中。这个不变量跨越了Vertex对象和Graph对象。多个类型的多个对象可以参与给定的不变量。

封装的例子

与类类似,数据结构得益于封装,它可以防止数据被意外修改。这是因为数据结构需要保留不变量,以便能够以承诺的方式运行,就像类一样。

In the graph data structure example, you would state that Vertex is a friend of Graph, and also make the constructors and data-members of Vertex private so that a Vertex can only be created and modified by Graph. In particular, Vertex would have a private constructor which accepts a pointer to its owning graph. This constructor is called in graph.addVertex(), which is possible because Vertex is a friend of Graph. (But note that Graph is not a friend of Vertex: there is no need for Vertex to be able to access Graph's vertex-list, say.)

术语

数据结构的定义本身就像一个类。我建议我们开始使用术语“广义类”来描述将数据从有效状态移动到另一有效状态的任何数据集、不变量和函数。c++类是广义类的一种特殊类型。不言而喻,友类是将封装从c++类扩展到广义类的精确机制。

(事实上,我希望用“广义类”的概念取代“类”,并使用“本机类”来表示编程语言支持的类的特殊情况。然后,在教授类时,您将学习本机类和这些广义类。但这可能会令人困惑。)

对于操作符<<和操作符>>,没有很好的理由让这些操作符成为朋友。它们确实不应该是成员函数,但它们也不需要是朋友函数。

最好的方法是创建公共打印(ostream&)和读取(istream&)函数。然后,根据这些函数写出操作符<<和操作符>>。这提供了额外的好处,允许您将这些函数设置为虚拟函数,从而提供虚拟序列化。

In C++ "friend" keyword is useful in Operator overloading and Making Bridge. 1.) Friend keyword in operator overloading :Example for operator overloading is: Let say we have a class "Point" that has two float variable"x"(for x-coordinate) and "y"(for y-coordinate). Now we have to overload "<<"(extraction operator) such that if we call "cout << pointobj" then it will print x and y coordinate (where pointobj is an object of class Point). To do this we have two option: 1.Overload "operator <<()" function in "ostream" class. 2.Overload "operator<<()" function in "Point" class. Now First option is not good because if we need to overload again this operator for some different class then we have to again make change in "ostream" class. That's why second is best option. Now compiler can call "operator <<()" function:

   1.Using ostream object cout.As: cout.operator<<(Pointobj) (form ostream class).
2.Call without an object.As: operator<<(cout, Pointobj) (from Point class).

Beacause we have implemented overloading in Point class. So to call this function without an object we have to add"friend" keyword because we can call a friend function without an object. Now function declaration will be As: "friend ostream &operator<<(ostream &cout, Point &pointobj);" 2.) Friend keyword in making bridge : Suppose we have to make a function in which we have to access private member of two or more classes ( generally termed as "bridge" ) . How to do this: To access private member of a class it should be member of that class. Now to access private member of other class every class should declare that function as a friend function. For example : Suppose there are two class A and B. A function "funcBridge()" want to access private member of both classes. Then both class should declare "funcBridge()" as: friend return_type funcBridge(A &a_obj, B & b_obj);I think this would help to understand friend keyword.

正如朋友声明的参考资料所说:

友元声明出现在类主体中,并授予函数或另一个类对出现友元声明的类的私有和受保护成员的访问权。

所以提醒一下,有些回答中有技术错误,说朋友只能访问受保护的成员。

@roo:这里没有破坏封装,因为类本身规定了谁可以访问它的私有成员。只有当这可能是由类外部引起时,封装才会被破坏,例如,如果你的操作符<<声明“我是类foo的朋友”。

朋友代替了公开的用法,而不是私人的用法!

实际上,c++ FAQ已经回答了这个问题。