【问题标题】:Preferring non-member non-friend functions to member functions更喜欢非成员非朋友函数而不是成员函数
【发布时间】:2011-09-21 21:41:40
【问题描述】:

这个问题的标题取自 Scott Meyers 在 Effective C++ 3rd Edition 中第 #23 项的标题。他使用以下代码:

class WebBrowser {
public:
    void clearCache();
    void clearHistory();
    void removeCookies();

    //This is the function in question.
    void clearEverything();
};

//Alternative non-member implementation of clearEverything() member function.
void clearBrowser(WebBrowser& wb) {
    wb.clearCache();
    wb.clearHistory();
    wb.removeCookies();
};

虽然声明下面的替代非成员非朋友函数比成员函数 clearEverything() 更适合封装。我想部分想法是,如果提供访问权限的成员函数较少,访问 WebBrowser 的内部成员数据的方法就会减少。

如果你接受这一点,并将这种函数作为外部的、非友元函数,你会把它们放在哪里?这些函数仍然与类紧密耦合,但它们将不再是类的一部分。将它们放在类的同一个 CPP 文件中、库中的另一个文件中是一种好习惯吗?

我主要来自 C# 背景,而且我从来没有摆脱对所有东西都成为课堂一部分的渴望,所以这让我有点困惑(虽然这听起来很傻)。

【问题讨论】:

    标签: c++


    【解决方案1】:

    通常,您会将它们放在关联的命名空间中。这与 C# 中的扩展方法(在某种程度上)提供相同的功能。

    问题是,在 C# 中,如果你想创建一些静态函数,它们必须在一个类中,这很荒谬,因为根本没有 OO 进行 - 例如 Math 类。在 C++ 中,您可以使用正确的工具来完成这项工作——命名空间。

    【讨论】:

      【解决方案2】:

      所以clearEverything 是一种并非绝对必要的便捷方法。但是否合适由您决定。

      这里的理念是类定义应该尽可能少,并且只提供一种完成某事的方法。这降低了单元测试的复杂性、将整个类换成替代实现的难度以及可能需要被子类覆盖的函数数量。

      一般来说,您不应该拥有只调用一系列其他公共成员函数的公共成员函数。如果这样做,则可能意味着:1)您的公共接口过于详细/细粒度或不合适,并且被调用的函数应设为私有,或 2)该函数实际上应该在类外部。

      汽车类比:喇叭通常与猛踩刹车结合使用,但是为了同时做这两个目的而添加一个新的踏板/按钮是很愚蠢的。结合Car.brake()Car.honk()Driver 执行的功能。但是,如果 Car.leftHeadLampOn()Car.rightHeadLampOn() 是两个独立的公共方法,则它可能是过度细粒度控制的示例,设计人员应该重新考虑为 Driver 提供单个 Car.lightsOn() 开关。

      在浏览器示例中,我倾向于同意 Scott Meyers 的观​​点,即它不应该是成员函数。但是,将它放在浏览器命名空间中也可能不合适。也许最好让它成为控制 Web 浏览器的事物的成员,例如GUI 事件处理程序的一部分。 MVC 专家可以随时从这里接手。

      【讨论】:

      • 赞成。我会向您指出斯科特关于处理放置位置的想法。他通过将这些辅助函数添加到不同的 .cpp/.h 文件中来参考 stdc 库如何做同样的事情,而您#include 只是您需要的那些。
      【解决方案3】:

      我经常这样做。我总是将它们放入与其他类成员函数相同的 .cpp 中。我不认为有任何二进制大小的开销取决于你把它们放在哪里。 (除非你把它放在标题中:P)

      【讨论】:

        【解决方案4】:

        如果你想走这条路,clearEverything 的实现应该放在类的标头(声明)和实现中——因为它们是紧密耦合的,似乎是放置它们的最佳位置。

        但是我倾向于将它们作为课程的一部分 - 因为将来您可能还有其他事情要清除,或者可能有更好或更快的实现来实现clearEverything例如删除数据库重新创建表

        【讨论】:

        • 如果您以后发现需要它们,您可以随时将它们添加到课程中,但您无法删除它们。如果您不需要 clearEverything() 访问类内部,请将其留在外面。如果让它访问内部变得很重要,请为该类编写一个成员函数(并且可以选择让clearEverything() 调用它)。