有效的 C++第 23 项 优先选择非成员非友元函数而不是成员函数

发布于 2024-11-07 04:12:13 字数 609 浏览 5 评论 0原文

虽然对类设计的一些事实感到困惑,特别是函数是否应该是成员,但我研究了Effective c++并找到了第23条,即,优先选择非成员非友元函数而不是成员函数。直接阅读网络浏览器示例是有道理的,但是该示例中的便利函数(在书中称为非成员函数)会更改类的状态,不是吗?

  • 那么,第一个问题,那么他们不应该是会员吗?

  • 进一步阅读,他考虑了STL函数,实际上一些类没有实现的函数是在stl中实现的。遵循本书的思想,它们演变成一些方便的函数,这些函数被打包到一些合理的命名空间中,例如来自 algorithm< 的 std::sortstd::copy /代码>。例如,vector 类没有 sort 函数,并且使用 stl sort 函数,因此它不是矢量类的成员。但是我们也可以将相同的推理延伸到向量类中的一些其他函数,例如分配,这样它也不能作为成员实现,而是作为便利函数实现。然而,这也会改变对象的内部状态,比如它所操作的排序。那么这个微妙但重要的(我猜)问题背后的基本原理是什么?

如果您可以阅读这本书,您能为我进一步澄清这些要点吗?

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 from algorithm. 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. 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. 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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(7

千柳 2024-11-14 04:12:13

完全没有必要访问这本书。

我们在这里处理的问题是依赖重用

在设计良好的软件中,您尝试将项目彼此隔离以减少依赖性,因为依赖性是需要更改时需要克服的障碍。

在设计良好的软件中,您可以应用DRY原则(不要重复自己),因为当需要进行更改时,必须在十几个不同的地方重复它是痛苦且容易出错的。

“经典”的面向对象思维方式在处理依赖关系方面越来越糟糕。由于有很多很多的方法直接依赖于类的内部,最轻微的改变就意味着整个重写。事实并非如此。

在 C++ 中,STL(不是整个标准库)的设计目标明确:

  • 减少依赖关系,
  • 允许重用

因此,容器公开了定义良好的接口,这些接口隐藏了其内部表示,但仍然提供对它们封装的信息的充分访问以便可以在它们上执行算法。所有修改都是通过容器接口进行的,从而保证了不变量。

例如,如果您考虑排序算法的要求。对于 STL 使用的(通常)实现,它需要(从容器):

  • 对给定索引处的项目进行有效访问:随机访问
  • 交换两个项目的能力:非关联

因此,任何提供随机访问和不关联(理论上)适合通过(例如)快速排序算法进行有效排序。

C++中有哪些容器可以满足这个要求?

  • 基本的 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:

  • 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. For the implementation used (in general) by the STL, it requires (from the container):

  • 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 ?

  • the basic C-array
  • 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 ? Because std::list does not offer random access (informally myList[4] does not work), thus the sort from algorithm is not suitable.

情释 2024-11-14 04:12:13

我使用的标准是,如果一个函数可以通过成为成员函数而显着更有效地实现,那么它应该是一个成员函数。 ::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.

相对绾红妆 2024-11-14 04:12:13

我认为这条规则的原因是,通过使用成员函数,您可能会意外地过度依赖类的内部结构。改变一个类的状态不是问题。真正的问题是,如果您修改类中的某些私有属性,则需要更改的代码量。保持类的接口(公共方法)尽可能小,可以减少在这种情况下需要做的工作量,也可以减少对私有数据做奇怪的事情的风险,从而使实例处于不一致的状态。

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).

や莫失莫忘 2024-11-14 04:12:13

各种想法:

  • 当非成员通过类的公共 API 工作时很好,因为它减少了代码量:
    • 需要仔细监控以确保类不变性,
    • 如果重新设计对象的实现,则需要更改。
  • 如果这还不够好,非会员仍然可以成为朋友
  • 编写非成员函数通常不太方便,因为成员不是隐式在范围内的,但是如果您考虑程序的演变:
    • 一旦存在非成员函数并且意识到相同的功能对其他类型也有用,通常很容易将该函数转换为模板,并且使其不仅可用于两种类型,而且可用于任意未来类型也。换句话说,非成员模板允许比运行时多态性/虚拟调度更灵活的算法重用:模板允许称为 鸭子打字
    • 具有有用成员函数的现有类型鼓励剪切并粘贴到需要类似行为的其他类型,因为大多数转换函数以供重用的方法都要求每个隐式成员访问对特定对象进行显式访问,这对于程序员来说将是 30 多秒的乏味......
  • 成员函数允许使用 object.function(x, y, z) 表示法,恕我直言非常方便、富有表现力、直观。它们还可以更好地与许多 IDE 中的发现/完成功能配合使用。
  • 将成员函数和非成员函数分开可以帮助传达类的本质、不变性和基本操作,并在逻辑上对附加功能和可能的临时“便利”功能进行分组。考虑一下托尼·霍尔的智慧:

    “构建软件设计有两种方法:一种方法是使其简单到明显没有缺陷,另一种方法是使其复杂到存在明显的缺陷。没有明显的缺陷。第一种方法要困难得多。”

    • 在这里,非成员使用并不一定困难得多,但您必须更多地考虑如何访问成员数据和私有/受保护的方法以及原因,以及哪些操作是基本的。这种自我反省也会改进成员函数的设计,只是更容易偷懒:-/。
  • 随着非成员功能的复杂性扩展或获取额外的依赖项,这些功能可以移动到单独的标头和实现文件甚至库中,因此核心功能的用户只需“付费”使用他们想要的部分。< /p>

(Omnifarious 的答案是必读的,如果你不熟悉,就读三次。)

Various thoughts:

  • It's nice when non-members work through the class's public API, as it reduces the amount of code that:
    • 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.
  • 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....
  • Member functions allow the 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."

    • 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.)

与之呼应 2024-11-14 04:12:13

动机很简单:保持一致的语法。作为班级
进化或使用时,各种非会员便利功能将
出现;你不想修改类接口来添加一些东西
例如,像 toUpper 到字符串类。 (如果是
std::string,当然,你不能。)斯科特担心的是,当这个
发生这种情况时,你最终会得到不一致的语法:

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

通过仅使用自由函数,根据需要将它们声明为友元,所有
函数具有相同的语法。另一种选择是修改
每次添加便利函数时的类定义。

我并不完全相信。如果一个类设计得好,它就有一个基本的
功能,用户清楚哪些功能是其中的一部分
基本功能以及额外的便利功能
(如果存在的话)。在全球范围内,字符串是一种特殊情况,
因为它被设计用来解决许多不同的问题;
我无法想象很多班级都会出现这种情况。

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 of
std::string, of course, you can't.) Scott's worry is that when this
happens, you end up with inconsistent syntax:

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.

撞了怀 2024-11-14 04:12:13

所以,第一个问题,他们不应该是
成员比?

不,这不成立。在惯用的 C++ 类设计中(至少在Effective C++中使用的惯用语中),非成员非友元函数扩展了类接口。它们可以被视为类的公共 API 的一部分,尽管事实上它们不需要也没有对该类的私有访问权限。如果根据 OOP 的某些定义,此设计“不是 OOP”,那么,按照该定义,惯用的 C++ 就不是 OOP。

将同样的推理延伸到某些人身上
Vector类中的其他函数

确实,标准容器的一些成员函数本来可以是自由函数。例如,vector::push_back 是根据 insert 定义的,并且当然可以在没有对该类进行私有访问的情况下实现。但在这种情况下,push_back 是矢量实现的抽象概念 BackInsertionSequence 的一部分。此类通用概念贯穿特定类的设计,因此,如果您正在设计或实现自己的通用概念,则可能会影响您放置函数的位置。

当然,标准的某些部分可以说应该有所不同,例如 std::string 也有方式许多成员函数。但事情已经过去了,这些类是在人们真正适应我们现在所说的现代 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. 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.

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. In that case, though, push_back is part of an abstract concept, the BackInsertionSequence, 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.

难理解 2024-11-14 04:12:13

我认为 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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文