代码之家  ›  专栏  ›  技术社区  ›  Mutmansky

什么时候函数应该是成员函数?

c++
  •  25
  • Mutmansky  · 技术社区  · 15 年前

    我在我的公司有一位同事,我对他的看法非常尊重,但我根本不懂他喜欢的C++代码编写风格。

    例如,假设有一个类A,他将编写以下类型的全局函数:

    void foo( A *ptrToA ){}
    

    或:

    void bar( const A &refToA ){}
    

    看到这样的全球功能,我的第一反应是:“为什么这些成员不是a?”他会上下断定,这与C++中的良好实践相一致,因为FoO和BAR可以通过使用A.的公共接口来执行他们需要执行的所有操作。例如,他会说这完全与Scott Meyers有效C++建议一致。我发现很难将其与书中的第19项协调起来,该项基本上说所有内容都应该是成员函数,但有一些例外(operator<<和operator>,以及需要动态类型转换的函数)。此外,虽然我同意函数可以对的公共接口做它们需要做的事情,但在我看来,这很大程度上是因为人们编写的类具有类A中每个数据成员的getter和setter。因此,对于该公共接口,a是一个过度美化的结构,您当然可以对公共接口做任何事情。接口。就我个人而言,我认为这不应该被利用,我认为这应该被阻止。

    显然,这只可能在C++之类的语言中不是纯面向对象的语言,所以我想一种方法是,我的同事不喜欢纯面向对象的软件设计方法。有人知道任何支持这一最佳实践立场的文献吗?或者,是否有人同意这一点,并可能以与我的同事不同的方式向我解释,以便我看到光明?或者每个人都同意我目前的感觉,这只是没有多大意义?

    编辑: 让我给出一个更好的代码示例。

    class Car
    {
        Wheel frontLeft;
        Wheel frontRight;
        Wheel rearLeft;
        Wheel rearRight;
        Wheel spareInTrunk;
    
    public:
        void wheelsOnCar( list< Wheel > &wheels )
        {
            wheels.push_back( frontLeft );
            wheels.push_back( frontRight);
            wheels.push_back( rearLeft);
            wheels.push_back( rearRight);
        }
        const Wheel & getSpare(){ return spareInTrunk; }
        void setSpare( const Wheel &newSpare ){ spareInTrunk = newSpare; }
        // There are getters and setters for the other wheels too,
        //but they aren't important for this example
    };
    

    然后我会看到这样一个函数:

    void wheelsRelatedToCar( Car *aCar, list< Wheel > &wheels )
    {
        aCar->wheelsOnCar( wheels );
        wheels.push_back( aCar->getSpare() );
    }
    

    这是一个真实的例子,类和函数的名称当然会改变。为什么会有人想要 wheelsRelatedToCar 不是汽车的成员功能?在这个真实的例子中,汽车和车轮在同一个库中。全局函数是使用该库在特定应用程序的源文件中定义的,因此参数是该函数特定于应用程序的。我的回答是,这是一个完全合法的汽车操作,属于汽车类。有没有其他的视角来看待它(除了不喜欢使用面向对象设计的视角)?

    10 回复  |  直到 12 年前
        1
  •  21
  •   Michael Burr    15 年前

    Scott Meyers主张非成员函数经常改进封装:

    Herb Sutter和Jim Hyslop在“自给自足的标题”中也谈到了这一点(引用了Meyer的文章)。

    这些想法已在 3rd edition of Meyer's "Effective C++" ,“第23项:首选非成员非友元函数而不是成员函数”,以及 Sutter/Alexandrescu's "C++ Coding Standards" “44-更喜欢编写非成员非朋友函数”。

    我认为很多开发人员认为这是不直观的,可能有点争议。

        2
  •  20
  •   KeatsPeeks    15 年前

    Herb Sutter和Andrei Alexandrescu建议:

    避免会员费:在可能的情况下,更喜欢让非会员的功能成为非朋友。


    非成员非朋友函数:

    • 通过最小化依赖关系改进封装 :函数体不能依赖于类的非公共成员。
    • 减少耦合 通过分解整体类来释放可分离的功能
    • 提高遗传性 ,因为很难编写不知道操作是否为给定类型的成员的模板。

    现在,回答你的问题( 什么时候? ,这里有一个算法来确定函数是否应该是成员和/或朋友:

    If the function is one of the operators =, ->, [], or (), which must be members:
    => Make it a member
    
    Else if: 
        a) the function needs a different type as its left-hand argument (as do stream operators, for example); 
     or b) it needs type conversions on its leftmost argument; 
     or c) it can be implemented using the class's public interface alone:
    => Make it a nonmember (and friend if needed in cases a) and b) )
    
    If it needs to behave virtually:
    => Add a virtual member function to provide the virtual behavior, and implement the nonmember in terms of that
    
    Else: 
    => Make it a member
    

    参考文献:

    • H.Sutter和Andrei Alexandrescu . C++编码标准(Addison Wesley,2004)
    • 迈尔斯 . "非成员函数是如何改进封装的”(C/C++用户期刊,18(2),2000年2月)
    • 斯特鲁斯特普 . C++程序设计语言(特殊3ReDebug)(Addison Wesley,2000)。_§10.3.2、_§11.3.2、_§11.3.5、_§11.5.2、_§21.2.3.1
    • 萨特 . 例外C++(Addison Wesley,2000)。
    • 萨特 . 例外C++风格(Addison Wesley,2004)。
        3
  •  13
  •   Binary Worrier    15 年前

    好啊

    1. 你的伴侣是正确的,如果一个方法 不需要成为 上课,严格来说 不应是类的成员。
    2. 你说得对,把每件东西都暴露出来 在类上,这样您就可以编写全局的 功能错误。

    你的伙伴是正确的,因为如果通用方法不是类的一部分,那么它们更容易构建(例如,为什么std::find不是std::vector的成员?因为这样它就不适用于列表、地图等)。

    您也是正确的,因为过多的暴露会破坏封装。

    当你进入现实世界,开始编写应用程序,并在截止日期前完成工作时,你会发现每个应用程序都是一组相互竞争的需求。”我们需要把A、B和C调进来,但要到下个月底。这是不可能的,他们可以有B&C或A&C,但不能有A&B。或者他们可以有A&B的不理想版本和C的良好版本”。

    编写代码并没有什么不同,有许多法律和规则定义了理想的封装、通用性、内聚性等级别,但其中许多都是矛盾的,而且您花费了大量的时间试图满足所有这些级别,却什么也没做。

    我一直说,这些原则和“法律”实际上只是指导方针,尽你所能地遵循它们,找到你自己的水平,让它们与你相处得很好。…预计这些水平每6个月左右就会改变:)

    希望这有帮助。

        4
  •  12
  •   jmucchiello    15 年前

    你自己回答问题。如果全局函数只能使用类的光滑、流线型和无浮动的公共接口来操作,那么它们应该是全局的。如果类已经变形以使这些函数成为可能,那么它就不是那么好了。

        5
  •  5
  •   sbi    15 年前

    一种方法是:

    • 如果一个函数操纵一个对象的内部状态,那么这很好地表明这个函数可能是一个成员函数。
    • 如果一个函数在不改变其内部状态的情况下使用一个对象,那么这很好地表明这个函数可能是一个自由函数。

    然而,这并不意味着在所有情况下都要完全遵循这些准则是一个好主意。还有其他的考虑。例如,正如其他人所引用的那样,非成员职能经常与大众的信仰背道而驰 增加 封装。(当然,如果他们通过私有数据的getter/setter来处理对象的状态,那就更值得怀疑了。事实上,我发现getter/setter无论如何都有问题。见 this excellent article 关于这个话题。)

        6
  •  3
  •   Evgeny Panasyuk    12 年前

    除了已经提到的文章外,我认为值得从专家那里引用一些额外的意见:


    虽然我们可以使一个成员函数返回长度,但最好使它成为全局友元函数。如果我们这样做,我们最终将能够定义相同的函数来处理内置数组并实现更大的设计一致性。为了取悦标准委员会,我在STL中加入了一个成员职能。 我知道,开始、结束和规模应该是全球性的职能,但不愿意冒着与委员会再次斗争的风险。总的来说,有很多妥协让我感到羞愧。如果不做它们就很难成功,但当我遇到所有我做错的事情,同时又完全知道如何做对的时候,我的嘴里仍然有一种金属的味道。 . 毕竟,成功被高估了很多。我将在这里和那里指出STL中的错误设计:有些是出于政治考虑,但许多是由于我无法辨别一般原则而导致的错误。)


    • 本贾尼·斯特劳斯特卢普。C++程序设计语言:

    10.3.2辅助功能

    通常,一个类有许多与之关联的函数,这些函数不需要在类中定义。 因为他们不需要直接访问表示。例如:

    int diff(Date a,Date b); // number of days in the range [a,b) or [b,a)
    bool leapyear(int y);
    Date next_weekday(Date d);
    Date next_saturday(Date d);
    

    在类本身中定义这样的函数会使类接口复杂化,并增加在考虑对表示法进行更改时可能需要检查的函数的数量。 . 这些函数如何与上课日期“关联”?传统上,他们的声明是 只需将类日期声明放在同一个文件中,并且需要日期的用户可以通过包含定义接口的文件使它们全部可用。


    • 安德烈·亚历山大·斯库。 Comment :

    基本上,我认为非虚拟成员的功能 C++中一种不必要的胡思乱想,会影响人们的大脑,导致坏的结果。 计划,而且需要很多年才能磨灭 . 对于智能指针 尤其是,它们会造成更大的伤害。

        7
  •  1
  •   Igor    15 年前

    你的同事是对的。 wheelsRelatedToCar 不应该是的成员函数 class Car ,因为此操作甚至(仅)不适用于汽车。这个功能是用来处理汽车的 车轮列表中的其他内容。

    所以,它应该是其他更高级别类的成员函数,比如 class Mechanic class WheelReplacementOperation .如果你没有这样的课程(尽管我认为你应该有!),最好使此函数成为全局函数。

        8
  •  1
  •   Johann Gerell    13 年前

    虽然我同意这里的大多数其他答案,即在可能的情况下应该使用自由函数,但硬币的另一面也需要注意:实用程序员的观点。如果我不熟悉代码库,那么如果将给定类型的行为公开为成员函数,那么该行为的可发现性将无限高。思考 智能感知 . 你不能用免费的功能得到它。

        9
  •  0
  •   Daniel Daranas    15 年前

    所以你是说一根绳子不应该有 bool IsEmpty() const “公共成员只是因为它有” int Size() const “IsEmpty的定义和实现如下: Size() == 0 ?

    我不同意。对我来说,拥有额外的特性会给这个类增加更多的服务,如果它们不破坏理解的容易性,这通常是好的——在一般情况下,不一定如此。

        10
  •  -1
  •   Larry Watanabe    15 年前

    做出某个成员或非成员的决定的一部分还与复杂性和管理复杂性有关。

    例如,如果有20个或更少的函数需要与一个类关联,那么为什么不让它们成为所有成员函数呢?

    但是,当数字达到100或几百时,将这些函数组织成组并创建一个伪类通常会有所帮助,该伪类存储另一个类的实例变量,或者作为对另一个类进行操作的类/静态函数的持有者。

    例如,可能有一个文档对象,但有一个对其进行操作并包含编辑功能的编辑器类,而不是文档上的所有编辑功能。

    没有硬性或快速的规则——只有原则,如封装、管理复杂性、减少依赖性。其中一些涉及权衡,必须在应用它们的问题的背景下进行分析。