非会员非朋友功能与私人功能

时间:2009-06-28 19:33:52

标签: c++ refactoring

Herb Sutter表示,在C ++中编写方法的最面向对象的方法是使用非成员非友元函数。这是否意味着我应该采用私有方法并将它们变成非成员非朋友函数?这些方法可能需要的任何成员变量都可以作为参数传递。

示例(之前):

class Number {
 public:
  Number( int nNumber ) : m_nNumber( nNumber ) {}
  int CalculateDifference( int nNumber ) { return minus( nNumber ); }
 private:
  int minus( int nNumber ) { return m_nNumber - nNumber; }
  int m_nNumber;
};

示例(之后):

int minus( int nLhsNumber, int nRhsNumber ) { return nLhsNumber - nRhsNumber; }
class Number {
 public:
  Number( int nNumber ) : m_nNumber( nNumber ) {}
  int CalculateDifference( int nNumber ) { return minus( m_nNumber, nNumber ); }
 private:
  int m_nNumber;
};

我是否在正确的轨道上?是否应将所有私有方法移至非成员非朋友函数?什么应该是规则,否则会告诉你?

4 个答案:

答案 0 :(得分:17)

我相信自由功能并同意Sutter,但我的理解是相反的。并不是说你的公共方法应该依赖于自由函数而不是私有方法,而是你可以使用提供的公共接口在类之外用自由函数构建更丰富的接口。

也就是说,你不要将你的私有部分推到类之外,而是将公共接口减少到最小,这允许你以尽可能少的耦合构建其余的功能:只使用公共接口。 / p>

在您的示例中,如果可以根据其他操作有效地表示,我将在类外部移动的是CalculateDifference方法。

class Number { // small simple interface: accessor to constant data, constructor
public:
  explicit Number( int nNumber ) : m_nNumber( nNumber ) {}
  int value() const { return m_nNumber; }
private:
  int m_nNumber;
};
Number operator+( Number const & lhs, Number const & rhs ) // Add addition to the interface
{
   return Number( lhs.value() + rhs.value() );
}
Number operator-( Number const & lhs, Number const & rhs ) // Add subtraction to the interface
{
   return Number( lhs.value() - rhs.value() );
}

优点是,如果你决定重新定义你的数字内部(你可以用这么简单的类做的那么多),只要你保持你的公共接口不变,那么所有其他的功能都将在框。内部实现细节不会强制您重新定义所有其他方法。

困难部分(不在上面的简单示例中)确定您必须提供的最少接口。从前一个问题引用的文章(GotW#84)就是一个很好的例子。如果你仔细阅读它,你会发现在保持相同的功能和性能的同时,你可以大大减少std :: basic_string中的方法数量。计数将从103个成员函数下降到只有32个成员。这意味着类中的实现更改将仅影响32个而不是103个成员,并且由于保留了接口,因此不需要更改可以实现32个成员的其余功能的71个自由函数。

这一点很重要:它更加封装,因为您限制了实现更改对代码的影响。

离开原始问题,这是一个简单的例子,说明如何使用自由函数来改进类的更改的局部性。假设一个具有非常复杂的加法运算的复杂类。您可以使用它来实现所有运算符覆盖作为成员函数,或者您可以在内部轻松有效地仅实现其中一些,并将其余部分作为自由函数提供:

class ReallyComplex
{
public:
   ReallyComplex& operator+=( ReallyComplex const & rhs );
};
ReallyComplex operator+( ReallyComplex const & lhs, ReallyComplex const & rhs )
{
   ReallyComplex tmp( lhs );
   tmp += rhs;
   return tmp;
}

可以很容易地看出,无论原始operator+=如何执行其任务,免费operator+都能正确履行其职责。现在,对于课程的任何和所有更改,operator+=都必须更新,但外部operator+将在其余生中保持不变。

上面的代码是一个常见的模式,虽然通常不是通过常量引用接收lhs操作数并在内部创建临时对象,但可以更改它以使参数本身成为值副本,从而帮助编译器进行一些优化:

ReallyComplex operator+( ReallyComplex lhs, ReallyComplex const & rhs )
{
   lhs += rhs;
   return lhs;
}

答案 1 :(得分:11)

并非所有私有方法都应移至非成员非朋友功能,但不需要访问的私有方法应该是。你应该尽可能少地访问,尽可能地封装你的类。

我强烈建议您阅读Effective C++ from Scott Meyers,其中解释了为什么要这样做以及何时适当。

编辑:我想补充一点,对于私有方法然后对于公共方法来说这不太正确,尽管仍然有效。由于封装与您将通过修改方法而破解的代码量成比例,具有私有成员函数,即使它不需要访问数据成员。这是因为修改代码会破坏很少的代码,只会破坏你可以控制的代码。

答案 2 :(得分:2)

这个问题似乎已经addressed回答了另一个问题。

这些规则往往是优秀的仆人和坏主人,涉及交易。如果您应用您建议的转换,结果是否更加可维护?为什么?我相信预期的好处是通过减少直接处理对象私有数据的方法的数量,您可以更容易地理解它的行为,从而使维护更容易。

我不相信你的后例能达到这个目标。 [您可能会注意到上面的“之后”示例无论如何都不会编译,但这是另一个故事。]如果我们调整它以纯粹根据公共方法而不是内部状态实现外部函数(为值添加一个访问器)然后我想我们已经有了一些收获。是否足以保证工作。我的意见:没有。我认为当方法更新对象中的数据值时,所提出的移动到外部函数的好处变得更大。我希望实现少数不变的维护变异器并实现主要的“业务”方法 - 外部化这些方法是确保它们只能在变更器方面工作的一种方式。

答案 3 :(得分:0)

这在很大程度上取决于您的设计和情况。当我编写代码时,我实际上只将publicprotected函数放入类中。其余的是实现细节,它是.cpp文件的一部分。

我经常使用pImpl习语。在我看来,这两种方法都有以下好处:

  • 清洁界面设计

    处理您的界面的用户将更好地理解它,而无需深入研究实施细节,如果他们不需要。

  • 与实现脱钩

    如果在不更改界面的情况下更改.cpp文件中的内容,则只需重新编译一个.cpp文件(编译单元)并重新链接应用程序,这样可以加快构建时间。

  • 隐藏使用您的界面的其他类的依赖关系

    如果所有内容都放入头文件中,您有时必须包含其他标题,这些标题是“界面的一部分”,而其他标题则包括它们,无论它们是否不想。将实际实现放到编译单元将隐藏这些依赖关系。

但有时你会编写只有头的库或实现,因此你不能把东西放到编译单元中,因为这种方法不仅需要包含你的lib,还需要链接你的lib。