为什么我们将私有成员函数放在标题中?


16

为什么我们将私有成员变量放在C ++标头中的答案是,必须在声明实例的位置知道类的大小,以便编译器可以生成可在堆栈中适当移动的代码。

为什么我们需要将私有成员放在标题中?

但是,是否有任何理由在类定义中声明私有函数?

替代方案本质上是pimpl惯用语,但没有多余的间接性。

这种语言的功能不仅仅是历史错误吗?

Answers:


11

私有成员函数可能是virtual,并且在C ++(使用vtable)的常见实现中,类的所有客户端都必须知道虚拟函数的特定顺序和数量。即使一个或多个虚拟成员函数为,这也适用private

似乎这就像“把车摆在马前”,因为编译器实现的选择不应影响语言规范。但是,实际上,C ++语言本身是与使用vtables 的有效实现(Cfront)同时开发的。


4
“实现选择不应影响语言”几乎与C ++的口号完全相反。该规范不要求任何特定的实现,但是有许多专门为满足特别有效的实现选择的要求而制定的规则。
Ben Voigt 2014年

这听起来很合理。是否有资料来源?
Praxeolitic

@Praxeolitic:您可以阅读有关虚拟方法表的信息
Greg Hewgill

1
@Praxeolitic-查找“带注释的C ++参考手册”(stroustrup.com/arm.html)的旧副本-作者讨论了各种实现细节及其对语言的影响。
迈克尔·科恩

我不确定这是否能真正回答问题。对我来说,它只是解决了一个特定的方面(尽管很好),而将其余方面抛在了后面:为什么我们需要在标头中放置非虚拟私有成员函数?当然,纯粹为了保持一致性/简单性是一个论点-但理想情况下,我们也应具有机械和/或哲学原理。因此,我添加了一个答案,我相信这可以解释为这是一个非常有益的效果的精心设计选择。
underscore_d

13

如果允许将方法添加到其定义之外的类中,则任何人都可以在任何文件中的任何位置添加它们。

这将立即使所有客户端代码都能轻松访问私有和受保护的数据成员。

一旦完成了类定义,就无法将某些文件标记为由作者特别祝福以扩展它-只有平面翻译单元。因此,告诉编译器一组特定的方法是官方的,或者由类创建者认可的唯一合理方法是在类内部声明它们。


请注意,我们可以直接访问C ++中的内存,这意味着创建具有与您的类相同的内存布局的影子类型,添加我自己的方法(或只是将所有数据公开)和,通常是不容易的reinterpret_cast。或者,我可以找到您的私有函数的代码,或者反汇编它。或者在符号表中查找函数地址并直接调用或。

这些访问说明符不会尝试阻止这些攻击,因为这是不可能的。它们仅指示应该如何使用类。


1
类定义可以引用对客户端代码隐藏的功能容器实体。或者,可以将其反转,并且从客户端代码隐藏的完整传统类定义可以指定仅描述公共成员并可以显示其大小的可见接口。
Praxeolitic

1
当然可以,但是编译模型无法提供一种合理的方式来阻止客户端代码插入其自身版本的隐藏容器或带有额外访问器的其他可见接口。
没用

这是一个好点,但是对于所有不在头文件中的成员函数的定义来说,这不是真的吗?
Praxeolitic

1
但是,所有成员函数都在类内部声明。关键是您不能添加至少在类定义中没有声明的新成员函数(并且一个定义规则可以防止多个定义)。
没用的2014年

那么,一个容器的私有函数规则呢?
Praxeolitic

8

接受的答案针对虚拟专用功能对此进行了解释,但这仅回答了问题的一个特定方面,这比OP要求的要多得多。因此,我们需要改写:为什么要求在标头中声明非虚拟私有函数?

另一个答案援引一个事实,即必须在一个块中声明类-在此之后它们将被密封并且不能添加到其中。通过省略标题中的私有方法,然后尝试在其他位置定义私有方法,您将可以做到这一点。好点。为什么该类的某些用户能够以其他用户无法观察到的方式扩展它?私有方法是其中的一部分,并不被排除在外。但是你问为什么它们包括在内,似乎有点重言。为什么班级用户必须了解他们?如果它们不可见,则用户将无法添加任何内容,请记住。

因此,我想提供一个答案,而不是默认情况下仅包含私有方法,而是提供一个特定的点,以使它们对用户可见。赫伯·萨特(Herb Sutter)的GotW#100(关于Pimpl惯用语)中给出了需要公开声明的非虚拟私有功能的机械原因,作为其基本原理的一部分。我不会在这里继续介绍Pimpl,因为我敢肯定我们都知道。但这是相关的一点:

在C ++中,当头文件类定义中的任何内容发生更改时,都必须重新编译该类的所有用户-即使唯一的更改是该类用户甚至无法访问的私有类成员。这是因为C ++的构建模型基于文本包含,并且因为C ++假定调用者知道有关类的两个主要问题,而这两个类可能会受到私有成员的影响:

  • 大小和布局:[成员和虚拟函数的名称-不言自明,性能出色,但我们为什么不在这里?
  • 函数:调用代码必须能够解析对类成员函数的调用,包括无法访问的私有函数这些私有函数会用非私有函数重载-如果私有函数更好地匹配,则调用代码将无法编译。(出于安全原因,C ++做出了谨慎的设计决策,在进行可访问性检查之前执行了重载解析。例如,人们认为将功能的可访问性从私有更改为公共不应改变合法调用代码的含义。)

作为委员会成员,萨特当然是一个非常可靠的消息来源,因此他一看到萨特就知道“一个故意的设计决定”。要求公开声明私有方法作为避免以后更改语义或意外破坏可访问性的一种方法可能是最令人信服的理由。值得庆幸的是,在此之前,整个事情似乎毫无意义!


但是然后您问为什么,这个答案似乎是重言式的。再次,为什么班级的用户需要了解他们? ”不,这不是重言式的。用户不一定“需要了解他们”。需要发生的是,您必须能够阻止人们在未经班级作者同意的情况下扩展班级。因此,必须预先声明所有此类成员。这使用户了解私人成员仅仅是一个必要的结果。
Nicol Bolas

1
@NicolBolas当然可以。就我而言,这可能不是很好的措辞。我的意思是,答案仅解释了(涵盖所有内容的)(非常有效)规则所导致的私有方法的可见性,而不是专门提供有关私有方法的可见性的理由。当然,真正的答案是Useless的,nasher的和我的的结合。我只想提供一个尚未出现的观点。
underscore_d 2016年

4

这样做有两个原因。

首先,要意识到访问说明符是针对编译器的,与运行时无关。访问范围之外的私有成员是编译错误。

简明

考虑一个短的函数,一行或两行。它的存在是为了减少代码在其他地方的复制,这还具有能够更改算法或其他任何东西在一个地方而不是在很多地方工作的优点(例如,更改排序算法)。

您是希望在标题中使用快速的一两行,还是在其中放置函数原型并在某处实现?在标题中更容易找到,而对于简短函数,拥有单独的实现则更为冗长。

还有另一个主要优势,那就是...

内联函数

私有函数可以被内联,这必然要求它在头文件中。考虑一下:

class A {
  private:
    inline void myPrivateFunction() {
      ...
    }

  public:
    inline void somePublicFunction() {
      myPrivateFunction();
      ...
    }
};

私有功能可以与公共功能一起内联。这是由编译器决定的,因为inline关键字在技术上是一个建议,而不是一个要求。


在类主体中定义的所有函数都将自动标记为inline,没有理由在那里使用关键字。
Ben Voigt 2014年

@BenVoigt就是这样。我没有意识到这一点,并且我一直在兼职使用C ++已经有一段时间了。这种语言永远不会让像我这样的小金块让我惊讶。

我认为没有方法需要在标头中进行内联。它只需要位于同一编译单元中。在某种情况下,为一个类拥有单独的编译单元是否有意义?
塞缪尔·丹尼尔森,

@SamuelDanielson正确,但是私有函数必须在类定义中。“私有”这个概念非常隐含它是该类的一部分。在.cpp文件中可能有一个非成员函数,该函数由在类定义之外定义的成员函数内联,但是这样的函数不是私有的。

问题的主体是“是否有任何理由在类定义中声明私有函数[原文如此,上下文表明OP确实意味着类声明]”。您正在谈论定义私有功能。这不能回答问题。@SamuelDanielson如今,LTO意味着功能可以在项目中的任何位置,并且保持被内联的机会均等。至于将一个类拆分为多个翻译单元,最简单的情况是该类很大,并且您想在语义上将其拆分为多个源文件。我敢肯定这样的大类是不鼓励的,但是无论如何
underscore_d

2

在头文件中拥有私有方法的另一个原因:在某些情况下,公共内联方法只不过是调用一个或多个私有方法而已。标头中包含私有方法意味着可以将对public方法的调用完全内联到私有方法的实际代码中,并且内联不会随着对私有方法的调用而停止。即使是从不同的编译单元(并且通常会从不同的编译单元调用公共方法)。

当然,还有一个原因是,如果编译器不知道所有方法(包括私有方法),就无法检测到重载解决方案的问题。


关于内联的好点!我想这与stdlib和其他使用内联定义方法的库特别相关。(对于内部代码来说,如果不是那么多,那么LTO可以在TU边界上做几乎所有事情)
underscore_d

0

允许这些功能访问私有成员。否则,friend无论如何您都需要在标题中添加它们。

如果任何函数可以访问该类的私有成员,那么私有将毫无用处。


1
也许我应该在这个问题上更加明确-我的意思是为什么这种语言被设计成这种方式?为什么一定是这种设计,或者为什么这个设计是好的?
Praxeolitic
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.