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

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


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


当前回答

首先(依我看)不要听那些说朋友没用的人。它是有用的。在许多情况下,您将拥有具有不打算公开可用的数据或功能的对象。对于许多作者可能只是表面上熟悉不同领域的大型代码库尤其如此。

友元说明符也有替代方案,但通常都很麻烦(cppp级别的具体类/掩码类型定义),或者不是万无一错(注释或函数名约定)。

在答案上;

友元说明符允许指定类访问发出友元语句的类内受保护的数据或功能。例如,在下面的代码中,任何人都可以询问孩子的名字,但只有母亲和孩子可以更改名字。

您可以通过考虑一个更复杂的类(如Window)来进一步考虑这个简单的示例。一个窗口很可能会有许多不应该被公开访问的函数/数据元素,但是被相关的类(如WindowManager)所需要。

class Child
{
//Mother class members can access the private parts of class Child.
friend class Mother;

public:

  string name( void );

protected:

  void setName( string newName );
};

其他回答

我使用friend的一个特定实例是在创建Singleton类时。friend关键字允许我创建一个访问器函数,这比总是在类上使用“GetInstance()”方法更简洁。

/////////////////////////
// Header file
class MySingleton
{
private:
    // Private c-tor for Singleton pattern
    MySingleton() {}

    friend MySingleton& GetMySingleton();
}

// Accessor function - less verbose than having a "GetInstance()"
//   static function on the class
MySingleton& GetMySingleton();


/////////////////////////
// Implementation file
MySingleton& GetMySingleton()
{
    static MySingleton theInstance;
    return theInstance;
}

看来我迟到了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++类扩展到广义类的精确机制。

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

在为类实现树算法时,教授给我们的框架代码将树类作为节点类的朋友。

它实际上没有任何好处,除了让你在不使用设置函数的情况下访问成员变量。

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.

友函数和类提供对类的私有和受保护成员的直接访问,以避免在一般情况下破坏封装。大多数使用是与ostream:我们希望能够键入:

Point p;
cout << p;

但是,这可能需要访问Point的私有数据,因此我们定义了重载操作符

friend ostream& operator<<(ostream& output, const Point& p);

然而,这里有明显的封装含义。首先,现在友类或函数可以完全访问类的所有成员,甚至不属于它的需要。其次,类和友元的实现现在交织在一起,以至于类中的内部更改可以破坏友元。

如果您将好友视为类的扩展,那么从逻辑上讲,这就不是问题。但是,在这种情况下,为什么有必要首先把朋友挖出来呢?

在不破坏封装的情况下,要实现“friends”所宣称的相同目标,可以这样做:

class A
{
public:
    void need_your_data(B & myBuddy)
    {
        myBuddy.take_this_name(name_);
    }
private:
    string name_;
};

class B
{
public:
    void print_buddy_name(A & myBuddy)
    {
        myBuddy.need_your_data(*this);
    }
    void take_this_name(const string & name)
    {
        cout << name;
    }
}; 

封装并没有被破坏,类B不能访问A中的内部实现,但结果与我们将B声明为A的朋友是一样的。 编译器将优化掉函数调用,因此这将导致与直接访问相同的指令。

我认为使用“朋友”是一个简单的捷径,有争议的好处,但一定的成本。