我一直在阅读c++常见问题,对朋友声明很好奇。我个人从未使用过它,但我对探索这门语言很感兴趣。
使用friend的一个好例子是什么?
读了一些常见问题,我喜欢<< >>操作符重载和添加作为这些类的朋友的想法。然而,我不确定这如何不会破坏封装。什么时候这些异常可以保持在OOP的严格范围内?
我一直在阅读c++常见问题,对朋友声明很好奇。我个人从未使用过它,但我对探索这门语言很感兴趣。
使用friend的一个好例子是什么?
读了一些常见问题,我喜欢<< >>操作符重载和添加作为这些类的朋友的想法。然而,我不确定这如何不会破坏封装。什么时候这些异常可以保持在OOP的严格范围内?
当前回答
看来我迟到了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++类扩展到广义类的精确机制。
(事实上,我希望用“广义类”的概念取代“类”,并使用“本机类”来表示编程语言支持的类的特殊情况。然后,在教授类时,您将学习本机类和这些广义类。但这可能会令人困惑。)
其他回答
你必须非常小心何时何地使用friend关键字,和你一样,我很少使用它。下面是一些关于使用friend和替代用法的注意事项。
假设你想比较两个对象,看它们是否相等。你可以:
使用访问器方法进行比较(检查每个ivar并确定是否相等)。 或者,您可以通过将所有成员设为public直接访问它们。
第一个选项的问题是,这可能会有很多访问器,这比直接变量访问(稍微)慢,更难读取,而且很麻烦。第二种方法的问题是完全破坏了封装。
如果我们可以定义一个外部函数,它仍然可以访问类的私有成员,那就更好了。我们可以用friend关键字做到这一点:
class Beer {
public:
friend bool equal(Beer a, Beer b);
private:
// ...
};
equal(Beer, Beer)方法现在可以直接访问a和b的私有成员(可能是char *brand, float percentAlcohol等)。这是一个相当做作的例子,您可能会更早地将friend应用于重载的==操作符,但我们会讲到这一点。
有几件事需要注意:
A friend is NOT a member function of the class It is an ordinary function with special access to the private members of the class Don't replace all accessors and mutators with friends (you may as well make everything public!) Friendship isn't reciprocal Friendship isn't transitive Friendship isn't inherited Or, as the C++ FAQ explains: "Just because I grant you friendship access to me doesn't automatically grant your kids access to me, doesn't automatically grant your friends access to me, and doesn't automatically grant me access to you."
我只有在用另一种方法更难的时候才会用朋友。另一个例子是,由于Mat2x2, Mat3x3, Mat4x4, Vec2, Vec3, Vec4等的互操作性,许多向量数学函数经常被创建为朋友。而且做朋友比到处使用访问器要容易得多。正如所指出的,friend在应用于<<(非常方便调试)、>>和==运算符时通常很有用,但也可以用于这样的事情:
class Birds {
public:
friend Birds operator +(Birds, Birds);
private:
int numberInFlock;
};
Birds operator +(Birds b1, Birds b2) {
Birds temp;
temp.numberInFlock = b1.numberInFlock + b2.numberInFlock;
return temp;
}
就像我说的,我不经常使用friend这个词,但偶尔它正是你所需要的。希望这能有所帮助!
可能我从上面的答案中漏掉了一些东西,但是封装中另一个重要的概念是隐藏实现。减少对私有数据成员(类的实现细节)的访问,可以更容易地修改代码。如果朋友直接访问私有数据,对实现数据字段(私有数据)的任何更改都会破坏访问该数据的代码。使用访问方法可以很大程度上消除这种情况。我认为相当重要。
看来我迟到了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++类扩展到广义类的精确机制。
(事实上,我希望用“广义类”的概念取代“类”,并使用“本机类”来表示编程语言支持的类的特殊情况。然后,在教授类时,您将学习本机类和这些广义类。但这可能会令人困惑。)
您使用私有/受保护/公共权限控制成员和函数的访问权限? 所以假设这3个层面中的每一个都很清楚,那么很明显我们遗漏了一些东西……
例如,将成员/函数声明为protected是非常通用的。你的意思是,这个功能对每个人来说都是遥不可及的(当然,继承的孩子除外)。但是异常呢?每个安全系统都让你有某种类型的“白名单”,对吗?
所以,“朋友”让你拥有坚如磐石的对象隔离的灵活性,但也允许为你认为合理的事情创造一个“漏洞”。
我想人们之所以说不需要它,是因为总有一种设计可以不需要它。我认为这类似于讨论全局变量:你永远不应该使用它们,总有办法不用它们……但在现实中,你会看到在某些情况下,这是(几乎)最优雅的方式……我认为朋友之间也是如此。
它实际上没有任何好处,除了让你在不使用设置函数的情况下访问成员变量
但这并不是正确的看法。 其理念是控制WHO可以访问什么,是否有设置功能与此关系不大。
编辑:阅读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,这有什么意义呢?(提示:事实并非如此)
底线: 封装是关于能够控制哪些函数可以访问私有成员。这与这些函数的定义具体位于何处无关。