简体   繁体   English

有效的C ++项目23首选非成员非友函数比成员函数

[英]Effective C++ Item 23 Prefer non-member non-friend functions to member functions

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. 在迷惑类设计的一些事实,特别是函数是否应该为成员时,我研究了Effective c ++并发现了第23项,即,将非成员非友函数优先于成员函数。 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? 通过Web浏览器示例直接阅读该书是有道理的,但是该示例中的便捷功能(在书中称为非成员函数)会更改类的状态,不是吗?

  • 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. 进一步阅读,他考虑了STL函数,实际上某些未由某些类实现的功能在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 from algorithm . 遵循本书的思想,它们演变为一些便利功能,这些便利功能包装在一些合理的名称空间中,例如来自algorithm std::sortstd::copy For instance vector class does not have a sort function and one uses the stl sort function so that is not a member of the vector class. 例如, vector类没有sort函数,而使用stl sort函数,因此它不是向量类的成员。 But one could also stretch the same reasoning to some other functions in vector class such as assign so that could also not be implemented as a member but as a convenience function. 但是,也可以将相同的推理扩展到向量类中的某些其他函数(例如, assign以便也不能实现为成员,而是实现为便捷函数。 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? 如果您可以使用这本书,可以为我进一步阐明这些观点吗?

Access to the book is by no mean necessary. 绝对不需要访问该书。

The issues we are dealing here are Dependency and Reuse . 我们在这里处理的问题是DependencyReuse

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. 在设计良好的软件中,您应用了DRY原理(请勿重复自己),因为当需要进行更改时,不得不在十几个不同的位置重复进行操作既痛苦又容易出错。

The "classic" OO mindset is increasingly bad at handling dependencies. “经典”的OO心态在处理依赖项方面越来越糟糕。 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: 在C ++中,STL(不是整个标准库)的设计目标是:

  • cutting dependencies 削减依赖
  • allowing reuse 允许重复使用

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. 例如,如果您考虑sort算法的要求。 For the implementation used (in general) by the STL, it requires (from the container): 对于STL使用的(通常)实现,它需要(从容器中):

  • efficient access to an item at a given index: Random Access 在给定索引下有效访问项目:随机访问
  • the ability to swap two items: not Associative 交换两个项目的能力:不关联

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 ? C ++中哪些容器可以满足此要求?

  • the basic C-array 基本的C数组
  • 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 ? 为每一个重写(复制/粘贴/调整) sort会很浪费,不是吗?

Note, for example, that there is a std::list::sort method. 注意,例如,有一个std::list::sort方法。 Why ? 为什么呢 Because std::list does not offer random access (informally myList[4] does not work), thus the sort from algorithm is not suitable. 因为std::list不提供随机访问(非正式地myList[4]不起作用),所以从算法进行sort是不合适的。

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. ::std::sort不符合该定义。 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. 因此,您不能仅仅实现一堆getter和setter函数并称其为好函数。

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. AtoMerZ也是正确的,可以对非成员非友元函数进行模板化,并将其重用于其他类型。

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. 顺便说一句,你应该买一本有效的C ++,这是一本很棒的书,但不要总是遵守这本书的每一项内容。 Object Oriented Design both good practices (from books, etc.) AND experience (I think it's also written in Effective C++ somewhere). 面向对象的设计(包括书籍等)的良好实践和经验(我也认为它是用Effective C ++编写的)。

So, first question, should not they be members than? 因此,第一个问题,他们不应该成为会员吗?

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. 在惯用的C ++类设计中(至少在Effective C ++中使用的习惯用法中),非成员非友函数扩展了类接口。 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. 尽管它们不需要并且没有对该类的私有访问权,但是它们可以被视为该类的公共API的一部分。 If this design is "not OOP" by some definition of OOP then, OK, idiomatic C++ is not OOP by that definition. 如果根据OOP的某种定义,此设计不是“ OOP”,那么,根据该定义,惯用的C ++不是OOP。

stretch the same reasoning to some other functions in vector class 将相同的推理扩展到向量类中的其他一些函数

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 of insert , and certainly could be implemented without private access to the class. 例如, vector::push_back是根据insert定义的,当然可以在没有私有访问该类的情况下实现。 In that case, though, push_back is part of an abstract concept, the BackInsertionSequence , that vector implements. 在那种情况下,尽管push_back是向量实现的抽象概念BackInsertionSequence一部分。 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 . 当然,标准的某些部分可以说应该有所不同,例如std :: string有太多的成员函数 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. 但是已经完成了,这些类是在人们真正适应我们现在称为现代C ++风格的条件之前设计的。 The class works either way, so there's only so much practical benefit you can ever get from worrying about the difference. 该课程以任何一种方式进行,因此担心差异会带来很多实际的好处。

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. 例如,您不想修改类接口以向字符串类添加诸如toUpper之类的东西。 (In the case of std::string , of course, you can't.) Scott's worry is that when this happens, you end up with inconsistent syntax: (当然不能使用std::string 。)Scott担心的是,当这种情况发生时,最终会导致语法不一致:

s.insert( "abc" );
toUpper( s );

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. 我无法想象许多班级都会出现这种情况。

Various thoughts: 各种想法:

  • It's nice when non-members work through the class's public API, as it reduces the amount of code that: 当非成员通过类的公共API进行工作时,这很不错,因为它减少了以下代码的数量:
    • needs to be carefully monitored to ensure class invariants, 需要仔细监控以确保类别不变性,
    • needs to be changed if the object's implementation is redesigned. 如果重新设计对象的实现,则需要更改。
  • When that isn't good enough, a non-member can still be made a friend . 如果这还不够的话,仍然可以成为非会员的friend
  • Writing a non-member function is usually a smidgeon less convenient, as members aren't implicitly in scope, BUT if you consider program evolution: 编写非成员函数通常不太方便,因为考虑到程序的演变,成员并不是隐式作用域的,但是,
    • Once a non-member function exists and it is realised that the same functionality would be useful for other types, it's generally very easy to convert the function to a template and have it available not just for both types, but for arbitrary future types too. 一旦存在一个非成员函数,并且意识到相同的功能对其他类型将很有用,那么通常很容易将函数转换为模板,并且不仅对这两种类型都可用,而且对将来的任意类型也都可用。 Put another way, non-member templates allow even more flexible algorithm reuse than run-time polymorphism / virtual dispatch: templates allow something known as duck typing . 换句话说,与运行时多态性/虚拟调度相比,非成员模板允许更灵活的算法重用:模板允许进行称为鸭子类型的操作
    • An existing type sporting a useful member function encourages cut-and-paste to the other types that would like analogous behaviour because most ways of converting the function for re-use require that every implicit member access be made an explicit access on a particular object, which is going to be a more tedius 30+ seconds for the programmer.... 现有的具有有用成员功能的类型鼓励将其剪切并粘贴到其他具有类似行为的类型,因为大多数转换该函数以供重用的方法都要求对每个隐式成员访问都进行特定对象的显式访问,对于程序员而言,这将是30秒钟的烦恼。
  • Member functions allow the object.function(x, y, z) notation, which IMHO is very convenient, expressive and intuitive. 成员函数允许使用object.function(x, y, z)表示法,恕我直言,这是非常方便,表达和直观的。 They also work better with discovery/completion features in many IDE's. 它们还可以与许多IDE中的发现/完成功能一起更好地工作。
  • 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." “构建软件设计的方法有两种:一种方法是使其变得如此简单,以至于显然没有缺陷,另一种方法是使其变得如此复杂以至于没有明显的缺陷。第一种方法要困难得多。 ”。

    • Here, non-member usage isn't necessarily far more difficult, but you do have to think more about how you're accessing member data and private/protected methods and why, and which operations are fundamental. 在这里,非成员的使用并不一定要困难得多,但是您必须更多地考虑如何访问成员数据和私有/受保护的方法以及原因,以及哪些操作至关重要。 Such soul searching would improve the design with member functions too, it's just easier to be lazy about :-/. 这样的灵魂搜索也可以通过成员函数来改善设计,懒惰:-/更容易。
  • 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.) (Omnifarious的回答是必读的,如果对您来说是新的,则为三次。)

I think sort is not implemented as a member function because it's widely used, not only for vectors. 我认为sort不能作为成员函数实现,因为它被广泛使用,不仅用于向量。 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. 因此,我认为这是为了简化实施。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 支持Scott Meyer建议的C ++ IDE:优先选择成员之外的非成员非朋友功能 - C++ IDE that supports Scott Meyer's advice: Prefer non-member non-friend functions over members 我应该何时更喜欢非会员非朋友功能到会员功能? - When should I prefer non-member non-friend functions to member functions? 优先将非成员非朋友功能发送给成员函数 - Preferring non-member non-friend functions to member functions 非会员非朋友功能与私人功能 - Non-member non-friend functions vs private functions 大规模使用Meyer的建议更喜欢非会员,非朋友的功能? - Large scale usage of Meyer's advice to prefer Non-member,non-friend functions? 非朋友,非成员函数会增加封装? - Non-friend, non-member functions increase encapsulation? 使用非成员非友元函数代替成员函数:缺点? - Using non-member non-friend functions instead of member functions: disadvantages? 非成员非友元函数真的增加了封装性吗? - Do non-member non-friend functions really increase encapsulation? Scott Meyers建议偏爱非成员非朋友方法是否适用于对象构建? - Does Scott Meyers's advice to prefer non-member non-friend methods apply to object construction? C++ 成员变量被非成员函数覆盖 - C++ Member variables being overwritten by non-member functions
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM