大量使用Meyer的build议来select非会员,非朋友的function?

一段时间以来,我一直在devise我的类接口是最小的,喜欢命名空间包装的非成员函数超过成员函数。 基本上遵循斯科特·迈耶的文章如何非成员函数改进封装的build议 。

在一些小规模的项目中,我一直这样做的效果很好,但是我想知道它在更大的范围内工作的效果如何。 是否有任何大的,广受好评的开源C ++项目,我可以看看,也许参考强烈遵循这个build议?

更新:感谢所有的input,但是我并不是真正对意见感兴趣,而是在更大的范围内发现它在实践中的工作情况。 尼克的答案在这方面是最接近的,但我希望能够看到代码。 任何forms的实践经验(积极的,消极的,实际的考虑等)的详细描述也是可以接受的。

OpenCV库做到这一点。 他们有一个cv :: Mat类,呈现一个3Dmatrix(或图像)。 然后他们拥有了cv命名空间中的所有其他function。

OpenCV库是巨大的,在其领域被广泛认可。

我在这个项目上做了很多工作, 在我现在的公司里面最大的是200万行,但这不是开源的,所以我不能把它作为参考。 不过,我一般会说,我同意这个build议。 你可以把那些没有被严格包含的function分开的东西越多,你的devise就越好。

举例来说,考虑经典的多态性示例:带有子类的Shape基类和虚拟Draw()函数。 在现实世界中,Draw()将需要采用一些绘图上下文,并且可能会意识到正在绘制的其他事物的状态,或者一般情况下的应用程序。 一旦你把所有的东西都放在Draw()的每个子类实现中,你可能会有一些代码重叠,或者大部分实际的Draw()逻辑将在基类或其他地方。 然后考虑如果你想重新使用一些代码,你需要在界面中提供更多的入口点,并可能污染与绘graphics状无关的其他代码的function(例如:多形状图相关逻辑)。 不久之后,这将是一个混乱,你会希望你有一个绘制函数,而不是形状(和上下文,和其他数据),形状只是有完全封装的function/数据,而不是使用或引用外部对象。

无论如何,这是我的经验/build议,值得。

我认为非会员职能的好处随着项目规模的增加而增加。 标准库容器,迭代器和algorithm库就是这方面的certificate。

如果你可以将algorithm从数据结构中分离出来(或者用另一种方式来解释,如果你可以用对象的内部状态如何分解你的对象),你可以减less你的类之间的耦合,并更好地利用通用代码。

斯科特·迈耶斯(Scott Meyers)并不是唯一支持这一原则的作者; Herb Sutter也是如此,尤其是在Monoliths Unstrung ,结尾的准则是:

在可能的情况下,宁愿写作为非会员非友人的function。

我认为从这篇文章中不需要的成员函数的最好的例子之一是std::basic_string::find ; 它没有理由存在,真的,因为std::find提供了完全相同的function。

编写非成员非朋友函数的一个实际优点是这样做可以显着减less彻底testing和validation代码所花费的时间。

例如,考虑序列容器成员函数insertpush_back 。 至less有两种方法来实现push_back

  1. 它可以简单地调用insert (它的行为是按照insert的方式定义的)
  2. 它可以完成所有insert的工作(可能调用私有助手函数),而无需实际调用insert

显然,在实现一个序列容器时,你可能想要使用第一种方法。 push_back只是insert一种特殊forms,并且(据我所知),通过以其他方式实现push_back (至less不用于listdequevector ),你无法真正获得任何性能优势。

但是,要彻底testing这样的容器,必须分别testingpush_back :因为push_back是成员函数,所以它可以修改容器的任何和所有的内部状态。 从testing的angular度来看,您应该(必须)假设push_back是使用第二种方法实现的,因为它有可能使用第二种方法实现。 不能保证它是以insert方式实施的。

如果push_back作为非会员的非友人实现,它不能触及容器的任何内部状态; 它必须使用第一种方法。 当你为它写testing的时候,你知道它不能破坏容器的内部状态(假设实际的容器成员函数是正确实现的)。 您可以使用这些知识来显着减less需要编写的testing数量,以充分利用代码。

我也做了很多,这似乎是有道理的,它不会导致缩放问题。 (虽然我目前的项目只有40000 LOC)事实上,我认为它使代码更具可扩展性 – 它减less了类,减less了依赖性。 它有时需要你重构你的函数,使它们独立于类的成员 – 从而经常创build一个更通用的帮助函数库,你可以在其他地方轻松地重用。 我还要提到,许多大型项目的常见问题之一就是课程的膨胀 – 我认为宁愿非会员,非朋友的职能在这里也有帮助。

(我没有时间把它写得很好,下面是一个5分钟的脑转储,无论在各个不同的层面上,这个转储无疑可以被分解出来,但请说明概念和一般的主旨。)

我对乔纳森·格林斯潘(Jonathan Grynspan)所采取的立场有相当的同情,但是要对这个问题进行更多的说明,而不是在评论中能合理地做到。

首先 – 给Alf Steinbach一个“好的说法”,他说:“这只是过度简化的漫画,他们的观点可能看起来有些冲突,对于这个问题,我不同意Scott Meyers的看法,我看到他在这里过度概括,或者他是。

斯科特,赫布等正在提出这些观点的时候,很less有人理解这种取舍或替代scheme,而且他们这样做的力度是不相称的。 人们在代码演化过程中遇到了一些麻烦的麻烦,并对这些问题进行了合理的devise。 让我们回到以后是否存在缺点的问题,但是首先值得说的是,这个问题通常很less而且很less:非成员函数只是devise可重用代码的一个小方面,在企业级系统只是简单地写一些你已经放入成员函数的代码作为非成员,很less使非成员可重用。 对于他们来说,甚至很难expression那些既复杂又值得重复使用的algorithm,而且这些algorithm不会被严格地限制在他们所devise的类的特定范围之内,这足够奇怪,实际上不可思议的是,其他一些类将会支持相同的操作和语义。 通常,您还需要模板参数,或引入基类来抽象所需的操作集。 两者在性能上都有重要的意义,即内联和外联,客户端代码重新编译。

也就是说,如果操作是通过公共接口实现的,那么通常在更改实现时需要更less的代码更改和影响研究,而作为非好友的非成员系统地实施该操作。 有时候,它使得最初的实现更加冗长,或者以某种其他方式不太理想和可维护。

但是,作为一个试金石 – 这些非会员function中有多less与目前唯一适用的类别相同? 有多less人想通过模板(这意味着内联,编译依赖关系)或基类(虚函数开销)抽象他们的参数,以允许重用? 他们都不鼓励人们把它们视为可重用的,但是如果不是这样的话,一个class级上的操作就会被分散开来 ,这会阻碍开发人员对系统的看法:开发人员往往必须为自己制定一个相当令人失望的事实 – “哦 – 这将只适用于X级“。

底线:大多数成员函数不可能重复使用。 很多公司代码不会被分解成干净的algorithm,而是可能重复使用前者的数据。 20年后,这种划分并不是必需的,也不是有用的或可以想象的有用的。 这与get / set方法非常相似 – 它们在特定的API边界处是必需的,但在代码的所有权和使用本地化时可能构成不必要的冗长。

就我个人而言,我没有一个完全的或没有办法做到这一点,而是根据是否有任何可能的好处,决定什么是成员函数或非成员,潜在的可重用性与界面的本地性。

优先使用非成员非友元函数进行封装 除非您希望隐式转换适用于类模板非成员函数(在这种情况下,您最好使它们成为友元函数):

也就是说,如果你有一个类模板type<T>

 template<class T> struct type { void friend foo(type<T> a) {} }; 

和一个可隐式转换为type<T> ,例如:

 template<class T> struct convertible_to_type { operator type<T>() { } }; 

以下工作如预期:

 auto t = convertible_to_type<int>{}; foo(t); // t is converted to type<int> 

但是,如果你让foo成为一个非朋友function:

 template<class T> void foo(type<T> a) {} 

那么以下不起作用:

 auto t = convertible_to_type<int>{}; foo(t); // FAILS: cannot deduce type T for type 

既然你不能推导T那么函数foo就会从重载parsing集中移除,即:找不到函数,这意味着隐式转换不会触发。

正如文章所述,STL既有会员function,也有非会员function。 这不是因为他的偏好 – 主要是因为许多自由函数在迭代器上运行,迭代器不在类层次结构中(因为STL希望数组上的指针是第一类迭代器)。

我强烈反对C ++的这篇文章,因为不一致会令人讨厌,而在现代IDE中,intellisense将被打破。

但在C#中,使用扩展方法,这是非常好的build议。 在那里,你可以得到两全其美的好处 – 你可以使非成员函数看起来像成员函数。 他们也可以在不同的文件 – 获得这种做法的所有好处,没有不一致的缺点。