函数何时应该是成员函数?

时间:2009-10-28 16:27:58

标签: c++

我公司有一位同事,他的意见我非常尊重,但我根本无法理解他在C ++中编写代码的首选方式。

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

void foo( A *ptrToA ){}

或:

void bar( const A &refToA ){}

我看到这样的全球职能时的第一直觉是:“为什么这些A成员不是?”他会坚持认为这与C ++中的良好实践建议是一致的,因为foo和bar可以通过使用A的公共接口执行他们需要执行的所有操作。例如,他会认为这是完全一致的与Scott Meyers有效的C ++推荐。我发现很难将这与第19章中的第19项相协调,该项基本上说一切都应该是一个成员函数,但有一些例外(运算符<<和operator>>以及需要动态类型转换的函数)。此外,虽然我同意函数可以做他们需要用A的公共接口做的事情,但在我看来,这主要是人们为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不成为Car的成员函数?在这个真实的例子中,Car和Wheel在同一个库中。全局函数是使用该库在特定应用程序的源文件中定义的,因此该参数使该函数特定于应用程序。我的回答是,这是一辆完全合法的汽车操作,属于汽车级别。是否有其他观点来看待它(除了不喜欢使用面向对象设计的人之外)?

10 个答案:

答案 0 :(得分:21)

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

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

这些想法已在3rd edition of Meyer's "Effective C++"中重新发布(更精确的形式),“项目23:将非成员非朋友功能更喜欢成员函数”,以及Sutter/Alexandrescu's "C++ Coding Standards",“44 - 更喜欢写非会员非友好职能“。

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

答案 1 :(得分:20)

Herb Sutter&amp; 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

参考文献:

  • 小时。 Sutter和Andrei Alexandrescu 。 C ++编码标准(Addison-Wesley,2004)
  • S上。迈耶斯。 “非成员函数如何改进封装”(C / C ++ Users Journal,18(2),2000年2月)
  • B.Stroustrup 。 C ++编程语言(Special 3rdEdition)(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)。

答案 2 :(得分:13)

  1. 你的配偶是正确的,如果一种方法 不需要成为一个成员 上课,然后 - 严格来说 - 它 不应该是班级成员。
  2. 你是对的,揭露每一个项目 在一个类上,所以你可以写全局 功能,是错误的。
  3. 你的配合是正确的,因为如果它们不是类的一部分,那么构建泛型方法会更容易(例如,为什么std :: find不是std :: vector的成员?因为它不适用于列表,地图等)。

    你也是对的,因为暴露太多东西会破坏封装。

    当你走进现实世界并开始编写应用程序并开始工作到最后期限时,你会发现每个应用程序都是一组竞争要求。 “我们需要获得A,B和C,但到下个月底。这是不可能的,他们可以有B&amp; C,或A&amp; C,但不是A&amp; B.或者他们可以有一个不理想的A&amp; B版本和一个很好的C版本。

    编写代码没有什么不同,有很多法律和规则可以定义封装,通用性,凝聚力等理想水平,但是很多都是矛盾的,你花了很多时间试图满足它们所有你什么都得不到完成。

    我总是说这些原则和“法律”实际上只是指导方针,在可以的地方跟随它们,找到适合自己的水平。 。 。并期望这些水平每6个月左右改变一次:)

    希望这有帮助。

答案 3 :(得分:12)

你回答自己的问题。如果全局函数只能使用流畅,简化和未开发的类公共接口来操作,那么它们应该是全局的。如果这个类被变形以使这些功能成为可能,那么它就不那么好了。

答案 4 :(得分:5)

看待这个的一种方法是:

  • 如果一个函数操纵一个对象的内部状态,那么这个函数应该是一个成员函数就是一个很好的指示。
  • 如果一个函数使用一个对象而不改变它的内部状态,那么这个函数可能应该是一个自由函数。

但是,这并不意味着在所有情况下完全遵循这些指南是个好主意。还有其他考虑因素。例如,正如其他人引用的那样,与普遍看法相反的非成员函数经常增加封装。 (当然,如果他们通过getter / setter处理私有数据来处理对象的状态,那就更有问题了。事实上,我发现getter / setter无论如何都有疑问。请参阅this excellent article关于这个主题。)< / p>

答案 5 :(得分:3)

除了已经提到过的文章之外,我认为值得引用专家的其他几点意见:


  

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


  • Bjarne Stroustrup。 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);
  

在类本身中定义此类函数会使类接口复杂化并增加在考虑更改表示时可能需要检查的函数数量。   这些函数如何与类Date“关联”?传统上,他们的声明是   简单地放在与Date类声明相同的文件中,需要Dates的用户可以通过包含定义接口的文件使它们全部可用。


  • Andrei Alexandrescu。 Comment
  

基本上我的信念是非虚拟成员函数通常是   C ++中不必要的怪癖会惹恼人们的大脑,导致糟糕   程序,并需要很多年才能消失。对于智能指针   特别是,他们可以做更多的伤害。

答案 6 :(得分:1)

你的同事是对的。 wheelsRelatedToCar不应该是class Car的成员函数,因为此操作甚至不会(仅)应用于Car。此功能正在使用Car 对车轮列表进行其他操作。

因此,它应该是其他更高级别类的成员函数,例如class Mechanicclass WheelReplacementOperation。如果你没有这样的课程(虽然我认为你应该有!),最好让这个功能全局化。

答案 7 :(得分:1)

虽然我同意这里的大多数其他答案,但是在可能的情况下应该使用免费功能,还需要注意硬币的另一面:实用程序员的观点。如果我是代码库的新手,如果该行为作为成员函数公开,则给定类型的行为的可发现性将无限高。想想 intellisense 。你不会通过免费功能得到它。

答案 8 :(得分:0)

所以你说一个字符串不应该只有“bool IsEmpty() const”公共成员,因为它有“int Size() const”并且IsEmpty被定义并实现为Size() == 0

我不同意。对我来说,拥有额外的功能可以为课程增加更多的服务,如果它们不会破坏它的易懂性,这通常是好的 - 在一般情况下,这并不一定如此。

答案 9 :(得分:-1)

将某个成员或非成员作出某种决定的部分决定也与复杂性和管理复杂性有关。

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

但是,当数字运行到100或几百时,通常有助于将这些功能组织成组并创建一个伪类,该伪类可以存储另一个类的实例变量,也可以作为类/静态的持有者在另一个类上运行的函数。

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

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