friend 关键字(类/函数)如何打破 C++ 中的封装?
一些程序员说,“友元函数打破了 C++ 中的封装”。 一些程序员还说,“友元函数不会破坏封装;相反,它们自然地扩展了封装屏障”
这是什么意思?..
如果友元函数破坏了 C++ 中的封装,那么怎么办?
Some programmer said that, "a friend function break the encapsulation in C++". and some programmer also said, "Friend functions do not break encapsulation; instead they naturally extend the encapsulation barrier"
what does it mean?..
If a friend function breaks the encapsulation in C++ then how??
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(10)
引用 C++ FAQ 我认为它描述了这种情况朋友和封装得很好。
Quote from C++ FAQ which I think describes the situation with friend and encapsulation very well.
一些人说“朋友”破坏了封装的原因是因为封装数据和功能的全部目的是让其他任何需要看到数据的东西都可以,但朋友让其他一些类看到内部。
我个人认为朋友们不要打破封装,因为类不应该完全依赖,而应该相互依赖。
以汽车为例。
我们通常将其用作抽象模型,表示我们不需要知道发动机是如何工作的,就知道踩下油门踏板会使发动机运转。
这就是封装的整个思想,其中只有我们需要与类交互的函数才是我们唯一需要了解的函数。
然而,机械师课程肯定需要了解汽车的具体内部工作原理,但将机械师构建到汽车中是没有意义的。
这就是朋友发挥作用的地方。你让机械师成为发动机、刹车或任何需要修理的东西的朋友,他可以修理它。 如果他能做的只是踩油门/刹车,他就无法修复它。
The reason some say that "friends" break encapsulation is because the whole point of encapsulating data and functionality is so that nothing else that needs to see that data can, but friends let some other classes see inside.
I personally think friends do not break encapuslation because classes should not be completely dependent, but rather interdependent.
Take the analogy of a car.
We typically use this as a model of abstraction, saying we don't need to know how the engine works to know that pushing the gas pedal makes it go.
This is the whole idea of encapsulation, where only the functions we need to interact with a class are the only ones we need to know about.
However, a Mechanic class would definately need to know about the specific inner workings of the car, but it makes no sense to build a Mechanic into a Car.
This is where friends come in. You make the Mechanic a Friend of the engine, the brakes, or whatever needs to be fixed and he can fix it. He can't fix it if all he can do is press gas/brake.
封装意味着您无法看到里面的内容,而
friend
意味着您可以看到内部。 根据您的观点,friend
因此要么破坏封装(通过让朋友看到内部),要么扩展它(通过让开发人员只放宽对特定朋友的限制)。FWIW,一个好的经验法则是只有嵌套/内部类才应该声明为朋友; 这个规则可以概括为“没有远距离的朋友”,即类的朋友(如果有的话)应该在与类本身相同的头文件中声明。
Encapsulation means that you can't see what's inside, and
friend
means that you can see inside. Depending on your point of view,friend
therefore either breaks encapsulation (by letting the friend see inside), or extends it (by letting the developer relax the barrier to specific friends only).FWIW, a good rule of thumb is to say that only nested/inner classes should be declared as friends; this rule can be summarized as "no long-distance friends", i.e. a class's friends (if any) should be declared within the same header file as the class itself.
我发布了 Stroustrup 的C++ 的设计和演变的片段,
2.10 保护模型,第 10 页。 53.
[...]
I post a fragment from Stroustrup's The Design and Evolution of C++,
2.10 The Protection Model, pg. 53.
[...]
你们有两种不同的思想流派。
第一个 (Java & C# ...) :: 将每个函数抛出类的公共区域,无论该函数是否“需要”访问“私有”区域。
第二个(C++ ...) :: 提供“仅”所需的函数,以便此类能够生存,并在创建类型的不相关集合中提供更高级别的函数。
恕我直言:C++ 明确满足 OOP 目标。
You have two different schools of thought.
The first one (Java & C# ...) :: throw every function in the public area of the class, whether that function 'needs' to access the 'private' area or not.
The second one (C++ ...) :: provide 'only' the needed functions so this class can survive, and provide higher level functions in unrelated collection of the created type.
IMHO :: C++ satisfies OOP goals clearly.
您可以将 C++ 视为具有非常原始的访问控制系统 - 要么您可以访问所有成员(友元或成员函数),要么您只能访问公共成员(除...之外的所有其他成员),或者您可以访问公共和受保护的成员成员(...继承的特殊规则)。
抽象中的“封装”同样是一个原始的访问控制系统,只不过我们通常说“类的一部分”的代码是有特权的,并且可以以语言允许的所有方式操作对象。 不属于“类的一部分”的代码没有特权,必须使用更小的、可能已发布的公共接口。
那么,“朋友”是干什么用的呢? 如果你这样想的话,那就是为了编写特权代码,而不是成员函数。 这是必要的,因为由于技术原因有些东西不能成为成员函数。 我能立即想到的是运算符重载,您需要在 LHS 上进行转换,以及标准算法模板函数的专业化,例如 std::swap (后者不是一个问题,因为如果有一个公共交换函数,它就是不太可能有一个公共交换方法,但很高兴支持那些希望接口正交的人)。
那么问题是,拥有非成员函数的特权代码是否会破坏封装? 在 Java 中,您可能会说“是”,因为如果 Java 代码在类定义中,那么它显然是“类的一部分”,如果不在类定义中,则不是类的一部分。
在 C++ 中,情况不太清楚,因为成员函数定义不必首先出现在类定义中。 两者之间几乎没有什么区别:
我无法说服自己在任何有意义的意义上 bar “破坏封装”而 baz “保留封装”。 即使是很小程度的实用主义也表明 bar 和 baz 正在做完全相同的事情,而且即使 bar 不是成员函数,它显然也和 baz 一样是“类的一部分”。 唯一的区别是语法上的,与封装无关。
另一方面,显然“friend”可以用来完全打破封装,通过将您能想到的所有其他类命名为您的朋友(例如在 Java 中使所有成员公开或受包保护,或者在 C++ 中使所有成员公开) )。 “friend”确实需要使用类而不仅仅是方法,这样您就可以在适当的情况下将嵌套类声明为友元,并且拥有不嵌套的紧耦合类的小簇(也就是说:绘制比单个类更高级别的封装)。 如果您使用它来紧密耦合所有类,那么您最终可能会得到错误的代码。
这并不是真的因为“朋友”,因为所做的一切都为我提供了一种新的语法来公开我本来可以公开为“公共”的成员。 但如果我把“朋友”作为一种解决方法,并用它来突破我自己不完善的公共界面,那么“朋友”实际上给了我糟糕设计的借口。 我将来可能会发誓戒酒,并建议其他人也这样做,但这和我每次醒来宿醉时发誓戒酒的原因是一样的。 其他人可能足够聪明或足够幸运,能够享受到这些好处而不会产生重大副作用。
所以“朋友”本身就允许封装破坏,就像指针算术一样,就像酒精允许在凌晨 3 点掉进阴沟一样。 然而,通过谨慎和得体的设计,朋友的特定用途不应该也不需要破坏封装。 而且,如果任何潜在的未来雇主正在读这篇文章,当我喝酒时,我通常不会宿醉;-)
最终的问题是,在我所知道的每种语言中,接口的行为就像继承,从某种意义上说,类的公共接口包含它实现的所有接口的所有成员。 因此,类接口比任何其他接口都要大。 这并不是一件坏事,因为“is-a”关系是面向对象的关键。 此外,它与您发布接口时发生的情况很好地对应,即任何客户端都可能正在使用它。 它只是不能自然地满足许多设计的要求,即默认情况下类有一个小接口,但也提供一个更大接口“超级用户”。 因此,大界面是默认的,而“子用户”则坚持使用无聊的部分。
因此,friend 是一种为“超级用户”提供更大界面而不影响“子用户”界面的生硬工具。 然而,由于它是如此直率,它实际上只有在相关类全部设计在一起时才有效,并且关于类应该如何耦合的争论导致了关于什么“破坏封装”的分歧。 请记住,语言中的访问级别不应该强制封装,尤其是 C++ 是一种多范式语言。 所以C++做了一些努力来帮助程序员强制封装,但是程序员仍然要好好设计,并按照自己的编程原则使用可用的功能。
You can think of C++ as having a very primitive access control system - either you can access all members (friend or member function), or you can access only public members (everything else, except...) or you can access public and protected members (...special rules for inheritance).
"Encapsulation" in the abstract is likewise a primitive access control system, except that usually we say that code which is "part of the class" is privileged and can manipulate the object in all the ways the language allows. Code which is not "part of the class" is unprivileged and must use a smaller, probably published, public interface.
So, what is "friend" for? If you think of things this way, it's for writing privileged code which is not a member function. It's necessary because there are some things which for technical reasons cannot be member functions. The ones I can think of off-hand are operator overloads where you need conversion on the LHS, and specializations of standard algorithm template functions like std::swap (the latter being less of an issue, since if there's a public swap function, it's unlikely to be harmful for there to be a public swap method too. But it's nice to support those folks who want interfaces to be orthogonal).
The question then is, does it break encapsulation to have privileged code which is not a member function? In Java you'd probably say "yes", since Java code is clearly "part of the class" if it's in the class definition, and not part of the class if it's not.
In C++ it's a bit less clear, since member functions definitions don't have to be in the class definition to start with. There's very little difference between:
I cannot convince myself that in any meaningful sense bar "breaks encapsulation" whereas baz "preserves encapsulation". Even a tiny degree of pragmatism indicates that bar and baz are doing exactly the same thing, and furthermore that even though bar is not a member function, it clearly is "part of the class" just as much as baz is. The only differences are syntactic, and have nothing to do with encapsulation.
On the other hand, clearly "friend" can be used to completely break encapsulation, by naming every other class you can think of as your friend (like making all your members public or package protected in Java, or making all your members public in C++). "friend" does need to work with classes and not just methods, so that you can declare nested classes as friends where appropriate, and have small clusters of tightly-coupled classes which aren't nested (which is to say: draw the bounds of encapsulation at a higher level than that of a single class). If you use it to tightly couple all your classes, then you'll probably end up with bad code.
This isn't really because of "friend", since all that has done is given me a new syntax to expose members that I could have exposed as "public" anyway. But if I reach for "friend" as a workaround, and use it to break through my own inadequate public interfaces, then "friend" has in effect given me an excuse for poor design. I might swear off it in future, and advise others to do likewise, but this is for the same reason I swear off drink every time I wake up hung over. Others may be wise or lucky enough to enjoy the benefits without major side-effects.
So "friend", in and of itself allows encapsulation-breaking, just like pointer arithmetic does, and just like alcohol allows falling into the gutter at 3am. With care and decent design, however, particular uses of friend should not and need not be encapsulation-breaking. And, in case any potential future employers are reading this, when I drink I'm not normally hung over ;-)
Ultimately the issue is that in every language I know, interfaces behave like inheritance, in the sense that the class's public interface incorporates all the members of all the interfaces it implements. The class interface is therefore larger than any other. This is no a bad thing, since the "is-a" relationship is key to OO. Furthermore, it corresponds well with what happens which you publish an interface, which is that any client might be using it. It just doesn't naturally accommodate what a lot of designs call for, which is for classes by default to have a small interface, but to also offer a larger interface to "superusers". So instead the large interface is the default, and "subusers" stick to the boring parts.
So, friend is one blunt tool for offering a larger interface to "superusers" without affecting the "subuser" interface. Since it is so blunt, though, it really only works when the relevant classes are all designed together, and arguments about how coupled classes ought to be is what leads to disagreements as to what "breaks encapsulation". Remember that access levels in languages aren't supposed to enforce encapsulation, and C++ in particular is a multi-paradigm language. So C++ makes some effort to assist programmers in enforcing encapsulation, but the programmer still has to design well, and use the available features in accordance with his own programming principles.
friend 关键字允许其他类访问 C++ 中的私有和受保护的数据成员。 有些人认为这破坏了封装,其他人认为它将封装扩展到需要访问私有/受保护数据的少数外部类。 “破坏封装”意味着成员和方法通常出于某种原因是私有的,而friend关键字破坏了这种封装。
在代码中我看到friend关键字通常会破坏封装并降低代码质量,但偶尔也有使用得好的情况。
如果您以前从未见过,这里有一个关于类友谊和继承的简单教程:
友谊和继承
The friend keyword allows other classes to access private and protected data members in C++. Some people think this is breaking encapsulation, other people think that it's extending encapsulation to a small number of outside classes that need access to private/protected data. The "breaking encapsulation" means that members and methods are usually private for a reason, and the friend keyword breaks that encapsulation.
In the code I've seen the friend keyword usually breaks encapsulation and reduces code quality, but there are occasionally situations in which it's used well.
Here's a simple tutorial on class friendship and inheritance if you've never seen it before:
Friendship and Inheritance
一旦您声明了一个友元函数,访问说明符,因此,您的类的封装细节将可以由该友元函数访问,而该友元函数不是您的类的一部分/方法。 因此,您基本上将对内部数据结构和方法的一些控制委托给您“信任”的外部函数
As soon as you declare a friend function, access specifiers, and thus, encapsulated details of your class will become accessible by that friend function, which is not a part/method of your class. So, you basically delegate some control over internal data structures and methods to an external function that you 'trust'
友谊以牺牲一些封装为代价来促进凝聚力。 您仍然有一个封装单元(类及其朋友),尽管它的粒度较小。 但是,当替代方案是向类添加额外的功能时,这不一定属于类,使用友谊可以让您获得所需的访问权限,同时仍然保持类的内聚性。 封装不是 OOP 唯一的,甚至不是最重要的原则。
Friendship promotes cohesion at the expense of some encapsulation. You still have an encapsulated unit (the class and its friends), though it's less granular. But when the alternative is adding extra functionality to the class, that does not necessarily belong, the use of friendship allows you the access you need while still maintaining the cohesiveness of the class. Encapsulation is not the only, or even the most, important principle of OOP.
如果你在每个可以访问私有数据的人周围画一条线,那就是数据被封装的程度。
如果您使用
friend
关键字,则必须围绕更多内容绘制这条线。 因此,封装的扩展或破坏取决于您的观点。一般来说,我不鼓励使用
friend
。 相反,请考虑添加私有数据的访问器。If you draw a line around everyone who can access private data, that is the extent that the data is encapsulated.
If you use the
friend
keyword, that line has to be drawn around more things. Thus encapsulation is extended or broken, depending on your point of view.In general, I discourage the use of
friend
. Instead consider adding accessors for private data.