C++:为啥必须声明私有函数?

Posted

技术标签:

【中文标题】C++:为啥必须声明私有函数?【英文标题】:C++: Why must private functions be declared?C++:为什么必须声明私有函数? 【发布时间】:2012-07-16 13:20:35 【问题描述】:

为什么 C++ 中的类必须声明它们的私有函数?有实际的技术原因(它在编译时的作用是什么)还是仅仅是为了一致性?

【问题讨论】:

您是在问为什么必须声明函数本身,还是在问为什么必须为这些函数说 private @GManNickG 第一个。我只是不知道为什么包括我的 header 在内的其他类必须知道它的私有函数 privatevirtual 是正交概念。一个函数是private 的事实并不意味着它不是virtual(事实上,有一个完整的习惯用法是only private virtualpublic非虚拟成员函数),所以是的,存在会影响 vtable。 @DavidRodríguez-dribes 好点。我实际上不知道虚拟私有函数可以在子类中被覆盖,并让它们的基类调用新版本而不暴露可调用性(这甚至是一个词吗?) @Ancurio:你想要的词是“可访问性”。 【参考方案1】:

访问级别不影响可见性。私有函数对外部代码可见,并且可以通过重载决议来选择(这会导致访问冲突错误):

class A 
    void F(int i) 
public:
    void F(unsigned i) 
;

int main() 
    A a;
    a.F(1); // error, void A::F(int) is private

想象一下这行得通时的混乱:

class A 
public:
    void F(unsigned i) 
;

int main() 
    A a;
    a.F(1);


// add private F overload to A
void A::F(int i) 

但是将其更改为第一个代码会导致重载决议选择不同的函数。那么下面的例子呢?

class A 
public:
    void F(unsigned i) 
;

// add private F overload to A
void A::F(int i) 

int main() 
    A a;
    a.F(1);

或者这是另一个错误的例子:

// A.h
class A 
public:
    void g()  f(1); 
    void f(unsigned);
;

// A_private_interface.h
class A;
void A::f(int);

// A.cpp
#include "A_private_interface.h"
#include "A.h"

void A::f(int) 
void A::f(unsigned) 

// main.cpp
#include "A.h"

int main() 
    A().g();

【讨论】:

呃,这似乎只是指出重载规则需要更改。 @GManNickG 考虑可见性而不选择不可访问的方法? C++ 的工作方式有充分的理由。例如:***.com/questions/644461/… 我认为重载规则不需要考虑可访问性。他们可能只需要定义为只考虑在使用点之前声明的成员函数——所以如果你的私有成员函数在类定义中,那么它会停止 a.F(1) 在客户端代码中编译,如果它仅在A.cpp 中添加,它不会影响客户端代码,但会影响 .cpp。这已经是命名空间的工作方式(如果您添加更多重载,它们可能会被选中)。需要一些复杂性,例如两阶段查找会发生什么。 @SteveJessop 当然可以,但我认为这将是一个相当大的改变。例如,当前类成员的范围是整个类,甚至在声明类成员之前。我不认为复杂性是有道理的,因为它只解决了底层问题的一种特殊情况,即 C++ 编译模型,并且已经有解决这种特殊情况的变通方法。 我同意不需要更改,我只是按照指示想象混乱,我不认为这一切令人困惑。在类定义中声明的类成员当然可以像以前一样在类中的任何地方继续可见。通过这个假设的新机制添加的成员将具有不同的可见性,因为这是新机制的全部目的。【参考方案2】:

类的私有成员仍然是该类的成员,因此必须声明它们,因为其他公共成员的实现可能依赖于该私有方法。声明它们将允许编译器将对该函数的调用理解为成员函数调用。

如果您有一个仅在 .cpp 文件中使用的方法,并且不依赖于对该类的其他私有成员的直接访问,请考虑将其移至匿名命名空间。那么就不需要在头文件中声明了。

【讨论】:

是的,但是为什么规范说必须声明它们?你没有回答问题。已经有规则说函数必须在调用之前声明,所以不是这样。 (虽然命名空间提示是一个很好的提示)【参考方案3】:

您必须在类本身的定义中声明所有成员,以便编译器知道哪些函数允许成为成员。否则,第二个程序员可能(不小心?)出现并添加成员、犯错误并违反对象的保证,从而导致未定义的行为和/或随机崩溃。

【讨论】:

我想你刚刚描述了 Python 类 =)【参考方案4】:

我问为什么必须声明私有函数,因为它们不会添加任何东西(对象大小或 vtable 条目)让其他翻译单元知道

如果你仔细想想,这类似于在文件中声明一些函数static。它从外部看不到,但它对编译器本身很重要。编译器想知道函数的签名,然后才能使用它。这就是为什么你首先声明函数的原因。 请记住,C++ 编译器是一次性的,这意味着在使用之前必须声明所有内容。1

从程序员的角度来看,声明私有函数仍然不是完全没用的。想象一下 2 个类,其中一个是另一个的 friend。朋友分区的类2 需要知道该类的私有信息(这个讨论变得很奇怪),否则他们无法使用它。

至于为什么 C++ 是这样设计的,我先说有历史原因:你不能在 C 中对结构进行切片,被 C++ 采用,所以你不能对类进行切片(并被其他从 C++ 分支出来的语言所采用)。我也猜想它是关于简单性:想象一下设计一种编译方法是多么困难,在这种方法中你可以将类拆分到不同的头文件中,让你的源文件知道它,并防止其他人向你添加东西类。

最后一点是,private 函数可以影响 vtable 的大小。也就是说,如果他们是virtual


1其实不完全。如果类中有内联函数,它们可以引用稍后在同一个类中定义的函数。但可能这个想法是从单次传递开始的,后来添加了这个异常。

2特别是内联成员函数。

【讨论】:

是的,我习惯于通过使用静态 C 函数来隐藏 C 中的实现,这就是为什么我希望在 cpp 中简单地声明(在任何实现之前)私有函数,而不是其他函数,就像 C 中的静态声明一样 @Ancurio,尽管你说的绝对有道理,但不幸的是,C++ 不支持将类定义一分为二。 在使用之前声明签名只是模糊地与为什么它们必须在类本身中有关。朋友区的类不需要知道其他的私有,它的成员函数定义需要知道私有。这也可以在课后/课外出现。您可以使用模板做一些有趣的事情,可以说将类定义一分为二。这提醒了我,具有内联函数的类定义两次,而不是一次。这就是为什么您可以在成员函数定义中使用成员/函数的原因在声明该成员之前 @MooingDuck,确实,我知道类声明本身的传递不是一次性的。但是,这仍然适用,因为大多数时候您在声明类之后定义类之外的函数。就像我在上面的评论中所说的那样,如果您可以将私有成员/函数的声明放在内部头文件中,那就太好了,但没有直接支持。 (您始终可以使用void *internal;,但这只是一种解决方法)。 @Shahbaz:那么,正如 OP 所问,为什么不支持它?【参考方案5】:

为什么必须声明私有函数有几个原因。

首次编译时错误检查

访问修饰符的目的是在编译时捕获某些类(没有双关语)的编程错误。私有函数是这样的函数,如果有人从类外部调用它们,那将是一个错误,您希望尽早了解它。

二次铸造和继承

取自 C++ 标准:

3 [注意:私有基类的成员可能无法作为继承的成员名称访问,但可以直接访问。由于指针转换 (4.10) 和显式转换 (5.4) 的规则,如果使用隐式转换,则从派生类的指针到指向不可访问基类的指针的转换可能是格式错误的,但格式正确如果使用显式转换。

第三位朋友

朋友们在那里私下互相展示。私有方法可以由另一个朋友类调用。

第 4 次一般理智和良好设计

曾与另外 100 名开发人员合作过一个项目。拥有一套标准和通用的规则有助于保持可维护性。声明私有内容对组中的其他人具有特定意义。

这也融入了良好的 OO 设计原则。什么可以公开,什么不可以

【讨论】:

这不是 Ancurio 想知道的。他想知道为什么我们不能(有些)一开始就将私有函数作为秘密。你不能调用你不知道的函数。 编译器需要知道它是私有的。因此,当其他人尝试调用它时,它可以发出编译时警告......没有什么是真正的秘密 @nate_weldon 确实如此,但是如果一开始就没有在公共头文件(以及文档)中声明私有非虚拟函数,那么没有人会尝试调用它。除了我自己……在这种情况下,警告(实际上不是错误吗?)确实有意义。 接受朋友...因为朋友会互相展示他们的隐私 来自标准 3 [ 注意:私有基类的成员可能无法作为继承的成员名称访问,但可以直接访问。由于指针转换 (4.10) 和显式转换 (5.4) 的规则,如果使用隐式转换,则从派生类的指针到指向不可访问基类的指针的转换可能是格式错误的,但格式正确如果使用显式转换。【参考方案6】:

有多种担忧,但是:

C++ 不允许您在初始定义后重新打开一个类以在其中声明新成员。 C++ 不允许您在组合形成程序的不同翻译单元中对类有不同的定义。

因此:

.cpp 文件想要在类中声明的任何私有成员函数都需要在 .h 文件中定义,该类的每个用户也可以看到。

从实际二进制兼容性的 POV 来看:正如 David 在评论中所说,私有 virtual 函数会影响此类以及使用它作为基础的任何类的 vtable 的大小和布局。所以编译器即使在编译不能调用它们的代码时也需要知道它们。

C++ 是否可以以不同的方式发明,以允许 .cpp 文件重新打开类并添加某些类型的附加成员函数,并在不破坏二进制兼容性的情况下实现所需的实现?是否可以放宽一个定义规则,以允许在某些方面有所不同的定义?比如静态成员函数和非虚非静态成员函数。

可能两者都是。我不认为存在任何技术障碍,尽管当前的 ODR非常 严格说明是什么使定义“不同”(因此对于允许外观非常相似的定义之间的二进制不兼容性的实现非常慷慨)。我认为在规则中引入这种例外情况的文本会很复杂。

最终可能归结为“设计师想要那样做”,或者可能是有人尝试了它并遇到了我没有想到的障碍。

【讨论】:

当前的模块提案对隐私问题有解决方案,仅仅是因为它们添加了另一个可见性“层”或“空间”:类本身、模块、“外部代码”。 private 将不会泄漏到模块定义之外,编译器仍然可以知道所有内容。 "C++ 不允许您在初始定义后重新打开一个类以在其中声明新成员。" 这是不正确的:14.7.3 显式特化 [temp.expl.spec] "以下任何一项的显式特化:(...) — 类或类模板的成员类模板 — 类或类模板的成员函数模板可以通过声明来声明(...)" "一个明确的特化应在包含特化模板的命名空间中声明。" @curiousguy:不确定我是否关注你。我不认为成员模板的专业化是“新成员”,因为模板是成员。我可能错了。如果我错了,那么模板当然是我所说的一个例外,因为除了专门化类外的成员模板之外,您当然还可以实例化类外的成员模板。在这两种情况下,我都不认为课程已经“重新开放”,而且我认为这对提问者没有帮助。 @SteveJessop 模板定义描述了一组潜在的无限特化。您可以将函数模板声明视为无限函数集的“潜在”声明,将成员模板声明视为无限成员集的“潜在”声明。您可以将新成员模板显式特化计为新成员或不计入新成员。我的观点是 1)成员模板显式特化在类定义之外声明 2)它的主体是成员函数的主体(如果它是函数模板)。 (...) "当然你也可以在类之外实例化成员模板。" 可以,但是你不能重写它们的定义。 “在这两种情况下,我都不认为课程已经“重新开放”,” 通过明确的专业化,课程可以“重新开放”。【参考方案7】:

一个原因是在 C++ 中朋友可以访问您的私人信息。为了让朋友访问它们,朋友必须了解它们。

【讨论】:

@Neal:嗯...这就是friends 的该死的目的

以上是关于C++:为啥必须声明私有函数?的主要内容,如果未能解决你的问题,请参考以下文章

避免在类头文件中声明私有函数 (C++)

C++ 析构函数:无法访问类中声明的私有成员

为啥 Rust 认为我的私有类型必须是公共的,除非我使用 pub(crate)?

从 C++ 中的类访问私有变量

在 C++ 中调用私有方法

C++ 为啥要使用公有、私有或受保护的继承?