有效的 C++第 23 项 优先选择非成员非友元函数而不是成员函数
虽然对类设计的一些事实感到困惑,特别是函数是否应该是成员,但我研究了Effective c++并找到了第23条,即,优先选择非成员非友元函数而不是成员函数。直接阅读网络浏览器示例是有道理的,但是该示例中的便利函数(在书中称为非成员函数)会更改类的状态,不是吗?
那么,第一个问题,那么他们不应该是会员吗?
进一步阅读,他考虑了STL函数,实际上一些类没有实现的函数是在stl中实现的。遵循本书的思想,它们演变成一些方便的函数,这些函数被打包到一些合理的命名空间中,例如来自
algorithm< 的
std::sort
、std::copy
/代码>。例如,vector
类没有sort
函数,并且使用 stlsort
函数,因此它不是矢量类的成员。但是我们也可以将相同的推理延伸到向量类中的一些其他函数,例如分配,这样它也不能作为成员实现,而是作为便利函数实现。然而,这也会改变对象的内部状态,比如它所操作的排序。那么这个微妙但重要的(我猜)问题背后的基本原理是什么?
如果您可以阅读这本书,您能为我进一步澄清这些要点吗?
While puzzling with some facts on class design, specifically whether the functions should be members or not, I looked into Effective c++ and found Item 23, namely, Prefer non-member non-friend functions to member functions. Reading that at first hand with the web browser example made some sense, however convenience functions( named the nonmember functions like this in the book) in that example change the state of the class, don't they?
So, first question, should not they be members then?
Reading a bit further, he considers the STL functions and indeed some functions which are not implemented by some classes are implemented in stl. Following the ideas of the book they evolve into some convenience functions that are packed into some reasonable namespaces such as
std::sort
,std::copy
fromalgorithm
. For instancevector
class does not have asort
function and one uses the stlsort
function so that is not a member of the vector class. But one could also stretch the same reasoning to some other functions in vector class such asassign
so that could also not be implemented as a member but as a convenience function. However that also changes the internal state of the object like sort on which it operated. So what is the rationale behind this subtle but important (I guess) issue.
If you have access to the book can you clarify these points a bit more for me?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(7)
完全没有必要访问这本书。
我们在这里处理的问题是依赖和重用。
在设计良好的软件中,您尝试将项目彼此隔离以减少依赖性,因为依赖性是需要更改时需要克服的障碍。
在设计良好的软件中,您可以应用DRY原则(不要重复自己),因为当需要进行更改时,必须在十几个不同的地方重复它是痛苦且容易出错的。
“经典”的面向对象思维方式在处理依赖关系方面越来越糟糕。由于有很多很多的方法直接依赖于类的内部,最轻微的改变就意味着整个重写。事实并非如此。
在 C++ 中,STL(不是整个标准库)的设计目标明确:
因此,容器公开了定义良好的接口,这些接口隐藏了其内部表示,但仍然提供对它们封装的信息的充分访问以便可以在它们上执行算法。所有修改都是通过容器接口进行的,从而保证了不变量。
例如,如果您考虑
排序
算法的要求。对于 STL 使用的(通常)实现,它需要(从容器):因此,任何提供随机访问和不关联(理论上)适合通过(例如)快速排序算法进行有效排序。
C++中有哪些容器可以满足这个要求?
deque
vector
以及您可能编写的任何容器(如果您注意这些细节)。
为每一个重写(复制/粘贴/调整)
sort
会很浪费,不是吗?例如,请注意,有一个
std::list::sort
方法。为什么 ?由于std::list
不提供随机访问(非正式地myList[4]
不起作用),因此sort
from 算法不适合。Access to the book is by no mean necessary.
The issues we are dealing here are Dependency and Reuse.
In a well-designed software, you try to isolate items from one another so as to reduce Dependencies, because Dependencies are a hurdle to overcome when change is necessary.
In a well-designed software, you apply the DRY principle (Don't Repeat Yourself) because when a change is necessary, it's painful and error-prone to have to repeat it in a dozen different places.
The "classic" OO mindset is increasingly bad at handling dependencies. By having lots and lots of methods depending directly on the internals of the class, the slightest change implies a whole rewrite. It need not be so.
In C++, the STL (not the whole standard library), has been designed with the explicit goals of:
Therefore, the Containers expose well-defined interfaces that hide their internal representations but still offer sufficient access to the information they encapsulate so that Algorithms may be executed on them. All modifications are made through the container interface so that the invariants are guaranteed.
For example, if you think about the requirements of the
sort
algorithm. For the implementation used (in general) by the STL, it requires (from the container):Thus, any container that provides Random Access and is not Associative is (in theory) suitable to be sorted efficiently by (say) a Quick Sort algorithm.
What are the Containers in C++ that satisfy this ?
deque
vector
And any container that you may write if you pay attention to these details.
It would be wasteful, wouldn't it, to rewrite (copy/paste/tweak)
sort
for each of those ?Note, for example, that there is a
std::list::sort
method. Why ? Becausestd::list
does not offer random access (informallymyList[4]
does not work), thus thesort
from algorithm is not suitable.我使用的标准是,如果一个函数可以通过成为成员函数而显着更有效地实现,那么它应该是一个成员函数。
::std::sort
不符合该定义。事实上,外部实施与内部实施没有任何效率差异。通过将某些功能实现为成员(或友元)函数可以显着提高效率,这意味着它可以从了解类的内部状态中受益匪浅。
接口设计艺术的一部分是找到最小的成员函数集,以便您可能想要在对象上执行的所有操作都可以根据它们合理有效地实现。并且这个集合不应该支持不应该在类上执行的操作。所以你不能只实现一堆 getter 和 setter 函数并称其为好。
The criteria I use is if a function could be implemented significantly more efficiently by being a member function, then it should be a member function.
::std::sort
does not meet that definition. In fact, there is no efficiency difference whatsoever in implementing it externally vs. internally.A vast efficiency improvement by implementing something as a member (or friend) function means that it greatly benefits from knowing the internal state of the class.
Part of the art of interface design is the art of finding the most minimal set of member functions such that all operations you might want to perform on the object can be implemented reasonably efficiently in terms of them. And this set should not support operations that shouldn't be performed on the class. So you can't just implement a bunch of getter and setter functions and call it good.
我认为这条规则的原因是,通过使用成员函数,您可能会意外地过度依赖类的内部结构。改变一个类的状态不是问题。真正的问题是,如果您修改类中的某些私有属性,则需要更改的代码量。保持类的接口(公共方法)尽可能小,可以减少在这种情况下需要做的工作量,也可以减少对私有数据做奇怪的事情的风险,从而使实例处于不一致的状态。
AtoMerZ 也是对的,非成员非友元函数也可以模板化并重用于其他类型。
顺便说一句,您应该购买《Effective C++》,这是一本很棒的书,但不要试图始终遵守本书的每一条。面向对象设计既有良好的实践(来自书籍等)也有经验(我认为它也是用Effective C++编写的)。
I think the reason for this rule is that by using member functions you may rely too much on the internals of a class by accident. Changing the state of a class is not a problem. The real problem is the amount of code you need to change if you modify some private property inside your class. Keeping the interface of the class (public methods) as small as possible reduces both the amount of work you will need to do in such a case and the risk of doing something weird with your private data, leaving you with an instance in an inconsistent state.
AtoMerZ is also right, non-member non-friend functions can be templated and reused for other types as well.
By the way you should buy your copy of Effective C++, it's a great book, but do not try to always comply with every item of this book. Object Oriented Design both good practices (from books, etc.) AND experience (I think it's also written in Effective C++ somewhere).
各种想法:
朋友
。object.function(x, y, z)
表示法,恕我直言非常方便、富有表现力、直观。它们还可以更好地与许多 IDE 中的发现/完成功能配合使用。将成员函数和非成员函数分开可以帮助传达类的本质、不变性和基本操作,并在逻辑上对附加功能和可能的临时“便利”功能进行分组。考虑一下托尼·霍尔的智慧:
“构建软件设计有两种方法:一种方法是使其简单到明显没有缺陷,另一种方法是使其复杂到存在明显的缺陷。没有明显的缺陷。第一种方法要困难得多。”
随着非成员功能的复杂性扩展或获取额外的依赖项,这些功能可以移动到单独的标头和实现文件甚至库中,因此核心功能的用户只需“付费”使用他们想要的部分。< /p>
(Omnifarious 的答案是必读的,如果你不熟悉,就读三次。)
Various thoughts:
friend
.object.function(x, y, z)
notation, which IMHO is very convenient, expressive and intuitive. They also work better with discovery/completion features in many IDE's.A separation as member and non-member functions can help communicate the essential nature of the class, it's invariants and fundamental operations, and logically group the add-on and possibly ad-hoc "convenience" features. Consider Tony Hoare's wisdom:
"There are two ways of constructing a software design: One way is to make it so simple that there are obviously no deficiencies, and the other way is to make it so complicated that there are no obvious deficiencies. The first method is far more difficult."
As non-member functionality expands in sophistication or picks up additional dependencies, the functions can be moved into separate headers and implementation files, even libraries, so users of the core functionality only "pay" for using the parts they want.
(Omnifarious's answer is a must-read, thrice if it's new to you.)
动机很简单:保持一致的语法。作为班级
进化或使用时,各种非会员便利功能将
出现;你不想修改类接口来添加一些东西
例如,像
toUpper
到字符串类。 (如果是std::string
,当然,你不能。)斯科特担心的是,当这个发生这种情况时,你最终会得到不一致的语法:
通过仅使用自由函数,根据需要将它们声明为友元,所有
函数具有相同的语法。另一种选择是修改
每次添加便利函数时的类定义。
我并不完全相信。如果一个类设计得好,它就有一个基本的
功能,用户清楚哪些功能是其中的一部分
基本功能以及额外的便利功能
(如果存在的话)。在全球范围内,字符串是一种特殊情况,
因为它被设计用来解决许多不同的问题;
我无法想象很多班级都会出现这种情况。
The motivation is simple: maintain a consistent syntax. As the class
evolves or is used, various non-member convenience functions will
appear; you don't want to modify the class interface to add something
like
toUpper
to a string class, for example. (In the case ofstd::string
, of course, you can't.) Scott's worry is that when thishappens, you end up with inconsistent syntax:
By only using free functions, declaring them friend as needed, all
functions have the same syntax. The alternative would be to modify the
class definition each time you add a convenience function.
I'm not entirely convinced. If a class is well designed, it has a basic
functionality, it's clear to the user which functions are part of
that basic functionality, and which are additional convenience functions
(if any such exist). Globally, string is sort of a special case,
because it is designed to be used to solve many different problems;
I can't imagine this being the case for many classes.
不,这不成立。在惯用的 C++ 类设计中(至少在Effective C++中使用的惯用语中),非成员非友元函数扩展了类接口。它们可以被视为类的公共 API 的一部分,尽管事实上它们不需要也没有对该类的私有访问权限。如果根据 OOP 的某些定义,此设计“不是 OOP”,那么,按照该定义,惯用的 C++ 就不是 OOP。
确实,标准容器的一些成员函数本来可以是自由函数。例如,
vector::push_back
是根据insert
定义的,并且当然可以在没有对该类进行私有访问的情况下实现。但在这种情况下,push_back
是矢量实现的抽象概念BackInsertionSequence
的一部分。此类通用概念贯穿特定类的设计,因此,如果您正在设计或实现自己的通用概念,则可能会影响您放置函数的位置。当然,标准的某些部分可以说应该有所不同,例如 std::string 也有方式许多成员函数。但事情已经过去了,这些类是在人们真正适应我们现在所说的现代 C++ 风格之前设计的。课程以两种方式进行,因此,担心差异所带来的实际好处是有限的。
No, this doesn't follow. In idiomatic C++ class design (at least, in the idioms used in Effective C++), non-member non-friend functions extend the class interface. They can be considered part of the public API for the class, despite the fact that they don't need and don't have private access to the class. If this design is "not OOP" by some definition of OOP then, OK, idiomatic C++ is not OOP by that definition.
That's true, there are some member functions of standard containers that could have been free functions. For example
vector::push_back
is defined in terms ofinsert
, and certainly could be implemented without private access to the class. In that case, though,push_back
is part of an abstract concept, theBackInsertionSequence
, that vector implements. Such generic concepts cut across the design of particular classes, so if you're designing or implementing your own generic concepts that might influence where you put functions.Certainly there are parts of the standard that arguably should have been different, for example std::string has way too many member functions. But what's done is done, and these classes were designed before people really settled down into what we now might call modern C++ style. The class works either way, so there's only so much practical benefit you can ever get from worrying about the difference.
我认为 sort 没有作为成员函数实现,因为它被广泛使用,不仅仅是向量。
如果他们将其作为成员函数,则每次使用它的每个容器都必须重新实现它。所以我认为这是为了更容易实施。
I think sort is not implemented as a member function because it's widely used, not only for vectors.
If they had it as a member function, they'd have to re-implement it each time for each container using it. So I think it's for easier implementation.