我一直在阅读c++常见问题,对朋友声明很好奇。我个人从未使用过它,但我对探索这门语言很感兴趣。
使用friend的一个好例子是什么?
读了一些常见问题,我喜欢<< >>操作符重载和添加作为这些类的朋友的想法。然而,我不确定这如何不会破坏封装。什么时候这些异常可以保持在OOP的严格范围内?
我一直在阅读c++常见问题,对朋友声明很好奇。我个人从未使用过它,但我对探索这门语言很感兴趣。
使用friend的一个好例子是什么?
读了一些常见问题,我喜欢<< >>操作符重载和添加作为这些类的朋友的想法。然而,我不确定这如何不会破坏封装。什么时候这些异常可以保持在OOP的严格范围内?
当前回答
在做TDD的时候,我经常使用c++中的'friend'关键字。
朋友能知道我的一切吗?
更新:我从Bjarne Stroustrup网站上找到了这个关于“朋友”关键字的有价值的答案。
“好友”是一种授予访问权限的显式机制,就像会员资格一样。
其他回答
friend关键字有很多好的用途。以下是我能立即看到的两种用法:
朋友的定义
友元定义允许在类作用域中定义函数,但该函数不会被定义为成员函数,而是被定义为外围命名空间的自由函数,并且除了依赖参数的查找之外通常不可见。这使得它对于操作符重载特别有用:
namespace utils {
class f {
private:
typedef int int_type;
int_type value;
public:
// let's assume it doesn't only need .value, but some
// internal stuff.
friend f operator+(f const& a, f const& b) {
// name resolution finds names in class-scope.
// int_type is visible here.
return f(a.value + b.value);
}
int getValue() const { return value; }
};
}
int main() {
utils::f a, b;
std::cout << (a + b).getValue(); // valid
}
私有CRTP基类
有时候,你会发现策略需要访问派生类:
// possible policy used for flexible-class.
template<typename Derived>
struct Policy {
void doSomething() {
// casting this to Derived* requires us to see that we are a
// base-class of Derived.
some_type const& t = static_cast<Derived*>(this)->getSomething();
}
};
// note, derived privately
template<template<typename> class SomePolicy>
struct FlexibleClass : private SomePolicy<FlexibleClass> {
// we derive privately, so the base-class wouldn't notice that,
// (even though it's the base itself!), so we need a friend declaration
// to make the base a friend of us.
friend class SomePolicy<FlexibleClass>;
void doStuff() {
// calls doSomething of the policy
this->doSomething();
}
// will return useful information
some_type getSomething();
};
在这个回答中,你会发现一个不做作的例子。在这个答案中使用了另一个代码。CRTP基类强制转换其this指针,以便能够使用数据成员指针访问派生类的数据字段。
可能我从上面的答案中漏掉了一些东西,但是封装中另一个重要的概念是隐藏实现。减少对私有数据成员(类的实现细节)的访问,可以更容易地修改代码。如果朋友直接访问私有数据,对实现数据字段(私有数据)的任何更改都会破坏访问该数据的代码。使用访问方法可以很大程度上消除这种情况。我认为相当重要。
在构建容器并希望为该类实现迭代器时,Friend可以派上用场。
典型的例子是重载操作符<<。另一个常见的用法是允许助手或管理类访问您的内部。
下面是我从c++朋友那里听到的一些指导原则。最后一个尤其令人难忘。
你的朋友不是你孩子的朋友。 你孩子的朋友并不是你的朋友。 只有朋友才能碰你的隐私部位。
看来我迟到了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++类扩展到广义类的精确机制。
(事实上,我希望用“广义类”的概念取代“类”,并使用“本机类”来表示编程语言支持的类的特殊情况。然后,在教授类时,您将学习本机类和这些广义类。但这可能会令人困惑。)