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

Mut*_*sky 25 c++

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

例如,鉴于有一些A类,他将编写该类型的全局函数:

void foo( A *ptrToA ){}
Run Code Online (Sandbox Code Playgroud)

要么:

void bar( const A &refToA ){}
Run Code Online (Sandbox Code Playgroud)

在看到这样的全球函数时,我的第一直觉是:"为什么不是A的这些成员?" 他会坚持认为这与C++中的良好实践建议是一致的,因为foo和bar可以通过使用A的公共接口执行他们需要执行的所有操作.例如,他会认为这是完全一致的与Scott Meyers有效的C++推荐.我发现很难将这与第19章中的第19项相协调,它基本上说一切都应该是一个成员函数,但有一些例外(operator <<和operator >>以及需要动态类型转换的函数).此外,虽然我同意的功能可以做他们需要一个公共接口做什么,在我看来,这主要是人写有getter和setter类因此与A级的每一个数据成员的结果公共接口,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
};
Run Code Online (Sandbox Code Playgroud)

然后我会看到这样的函数:

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

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

Mic*_*urr 21

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

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

这些想法在被重印(以更精致的形式)梅尔的"有效的C++"第三版,"第23项:不想非成员非友元函数成员函数",和萨特/ Alexandrescu的的"C++编码标准"," 44 - 更喜欢写非会员非友好职能".

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

  • 我并没有诋毁迈耶斯的选择.我想也许在这种情况下他应该更加小心.他本能地理解一个类的公共界面应该简化和最小化.这实际上是文章的真正含义.但是他专注于非成员函数的优点,以至于他忘记并非每个C++程序员都知道第一部分:首先简化公共接口.有一个重点和地方可以加强基础知识.提问者的"大师"朋友显然错过了森林里的文章树. (6认同)
  • 是的,但通过展示充满getter和setter的示例,然后从高处声明您通过非成员函数"改进"封装,也许有些人从文章中得到了错误的信息. (4认同)
  • Scott Meyers并未建议通过非成员函数制作大量的getter和setter来改进Encapsulation.他呢? (3认同)
  • 一般而言,吸毒者和二传手都是邪恶的,尽管他们比公共数据成员更好. (3认同)
  • 通常很难说明具体情况,许多类最终只是美化结构.但是,我会将这样的类与"blob"区分开来.blob包括将几乎没有关系的元素组合在一起,而将汽车的5个轮子组合在同一个对象中是非常合理的.最后,我总是区分两种类:具有明确责任性(例如处理资源)的类,以及实际上是相关对象聚合的类. (2认同)

Kea*_*eks 20

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)
  • 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
  • H.萨特.特殊的C++(Addison-Wesley,2000).
  • H.萨特.出色的C++风格(Addison-Wesley,2004).


Bin*_*ier 13

  1. 你的配偶是正确的,如果一个方法不需要是一个类的成员,那么 - 严格来说 - 它不应该是一个类的成员.
  2. 你是正确的,暴露一个类的每个项目,所以你可以编写全局函数,是错误的.

您的配合是正确的,因为如果它们不是类的一部分,那么构建泛型方法会更容易(例如,为什么std :: find不是std :: vector的成员?因为它不适用于列表,映射等) .

你也是对的,因为暴露太多而无法做到这一点会破坏封装.

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

编写代码没有什么不同,有很多定义封装,通用性,内聚等理想级别的法律和规则,但是其中很多都是矛盾的,你花了很多时间试图满足它们所有你没有做到的事情.

我总是说这些校长和"法律"实际上只是指导方针,在你可以的地方跟随它们,找到你自己与你相处的水平...并期望这些水平每6个月左右改变一次:)

希望这可以帮助.


jmu*_*llo 12

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


sbi*_*sbi 5

看待这个的一种方法是:

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

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