C++私有函数真的需要放在头文件中吗?

74

我一直将头文件视为描述类的一种“公共接口”,在这种情况下,最好将私有字段和函数保存在.cpp文件中。

我知道私有字段需要在头文件中声明,以便其他类可以知道类实例将占用多少内存,但是当我要编写一个私有辅助函数时,我想到这个函数可以被定义为静态函数,这样它就不需要成为类的一部分了。它可以轻松地成为类定义的.cpp文件中的常规函数。

我意识到所有私有函数都可以通过接受指向类字段的指针/引用来重写为静态函数,而无需期望在类中定义。

这将消除在头文件中声明任何私有函数的需要。

我喜欢遵循惯例,那么在C++中,非静态私有函数是否应该放在头文件中?静态函数或静态常量呢?

我会放一些代码来解释我的意思:

.h文件:

#ifndef SOME_CLASS_H
#define SOME_CLASS_H

class SomeClass
{
private:
    int x;
public:
    void combineWithX(int y);
};

#endif

.cpp 文件

#include "SomeClass.h"

void someHelper(int* x)
{
    *x = (*x) + 1;
}

void SomeClass::combineWithX(int y)
{
    someHelper(&x);
    x += y;
}

请注意,.cpp文件中的someHelper(int* x)函数间接引用了私有成员变量x,因此不需要出现在头文件中。我想知道这种写法是否被认为是“不好的风格”。


4
请注意,私有函数需要从类内部进行友元声明,否则它们将无法访问私有成员。如果它们不需要访问私有成员,那么这些函数本身可能根本不应该是成员函数。 - Mooing Duck
3
根据C++11 9.2-p2规定,一旦关闭一个类的定义,就无法再重新打开以扩展该类,因此,是的,您必须在该类的声明中将成员变量和成员函数进行配对。如果需要,可以使用“pimpl实现”(pimpl)方法。 - WhozCraig
@WhozCraig 谢谢,我借用了你的链接(最初我链接到一个更糟糕的问题)。 - M.M
@MooingDuck 私有函数可以放在私有内部类中,这样它们就不必在头文件中了。https://dev59.com/AV4c5IYBdhLWcg3wFW6N#28734794 - balki
1
请参考以下答案,了解为什么私有函数(即使它们是静态或非虚拟的)必须出现在类声明中的有意义的解释:https://softwareengineering.stackexchange.com/a/239175/221200 https://softwareengineering.stackexchange.com/a/324450/221200 - jcsahnwaldt Reinstate Monica
3个回答

51

私有辅助函数可以通过将它们移动到内部类中来隐藏在公共头文件之外。这是因为内部类被认为是类的一部分,可以访问周围类的私有成员。

PIMPL 惯用法不同,这没有任何动态分配或间接代价。编辑/重构私有函数时,编译时间应该更快,因为不需要重新编译所有文件,包括公共头文件。

示例:

公共.h文件

#ifndef SOME_CLASS_H
#define SOME_CLASS_H

class SomeClass
{
private:
    // Just forward declaring in public header.
    struct Private;
    int x;
public:
    void combineWithX(int y);
};

#endif

在 .cpp 文件中

#include "SomeClass.h"

// Declare all private member functions of SomeClass here
struct SomeClass::Private
{
  static void someHelper(SomeClass& self)
  {
    self.x = self.x + 1;
  }
};

void SomeClass::combineWithX(int y)
{
    Private::someHelper(*this);
    x += y;
}

2
“Private”需要声明为“SomeClass”的友元才能访问其私有成员吗?“friend struct Private”。我刚刚检查了您的代码是否可以编译,它确实可以,但我不明白为什么。普通的嵌套类/结构并没有获得其封闭类的任何特殊权限。 - GameSalutes
@GameSalutes 我没有标准参考,但这一直可以正常工作而没有任何警告。所以我猜嵌套结构确实可以完全访问封闭结构,就好像它是其中的一部分,而不必将其标记为“friend”。 - balki
6
我看到我混淆了我的作用域规则。原来,嵌套类具有与其他定义的数据成员相同的访问级别限制,因此它可以访问封闭类的私有成员。我当时想到的是相反的情况,即封闭类无法访问嵌套类的内部。这在标准(cpp17 n4659)-12.2.5中得到保证,涵盖了嵌套类声明和访问允许,包括前向声明。这是一个很好的论坛帖子:http://www.cplusplus.com/forum/general/181016/。我将使用它,因为它更好地封装了私有成员函数。 - GameSalutes
所以我们不需要在头文件中声明私有函数,但是我们需要声明“Private”……这样不是最终结果都一样吗? - devoured elysium
1
@devouredelysium 主要优点是,当您想要添加/删除/修改私有函数时,无需更改头文件,因此无需重新编译包含此头文件的所有文件。 - balki
显示剩余2条评论

21

我认为,将实现细节暴露在头文件中是一个问题;这会干扰接口和实现的分离。

如果那些函数需要访问私有成员变量,则将私有助手函数移动到 .cpp 文件中作为自由函数(我猜这就是你所说的 "static")是行不通的。

您可能会对查看pImpl惯用语感兴趣(更多)


如果这些函数需要访问私有成员变量,那么它们将无法工作,这很不幸。像Go这样的语言将私有变量基于模块而非类进行处理,我认为这更有意义。如果C++也是这种情况,那么私有辅助函数也将成为可能。 - David Callanan

2
有时候我看到人们使用两个不同的头文件,一个公共头文件和一个私有头文件,就像这样:

// SomeClass.h - - - -   

SomeClass 
{
public:
   // public functions    
}

// SomeClass_.h - - - -

SomeClass
{
public:
    // public functions
private:
    // private functions
}

// SomeClass.cpp - - - -

#include "SomeClass_.h"

// SomeClass implementation

这样做的好处是公共头文件不会被私有函数声明混淆,但需要开发者维护每个函数签名的3份副本。头文件一直被许多人普遍认为存在问题,不仅因为它们需要额外的维护开销,而且因为它们对构建环境产生的影响。很容易编写代码或设置构建选项,导致系统或外部头文件无法编译,从而导致大量潜在的模糊错误。C++20 模块是一个值得探索的替代方案。

1
这不会改变类的大小吗? - Sebastian
@Sebastian,我认为Shavais的意思是我们将私有方法放在ifdef块中,而不是任何数据成员。我仍然不确定它是否会引发未定义的行为-从一瞥中,如果私有方法之后没有公共方法,我看不出问题会是什么。 - Elliott
@Elliott 如果两个类的大小相同,可以使用静态继承来实现布局兼容,以避免未定义行为? - Sebastian
我删除了 #ifdef 块,因为那完全忽略了简化头文件的重点。我认为数据成员通常不会在公共和私有头文件之间更改,因为这会改变 sizeof 的报告,正如你所说的那样。很遗憾看到这个答案被投票否决了这么多次,它似乎是一个完全好的答案;公共和私有头文件肯定是相当常见的。而且我不可能是唯一一个认为头文件有问题的人,因为 C++20 专门引入了一个新特性(模块)来试图缓解头文件的问题。 - Shavais

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接