为什么我们将私有成员变量放在C ++标头中的答案是,必须在声明实例的位置知道类的大小,以便编译器可以生成可在堆栈中适当移动的代码。
但是,是否有任何理由在类定义中声明私有函数?
替代方案本质上是pimpl惯用语,但没有多余的间接性。
这种语言的功能不仅仅是历史错误吗?
为什么我们将私有成员变量放在C ++标头中的答案是,必须在声明实例的位置知道类的大小,以便编译器可以生成可在堆栈中适当移动的代码。
但是,是否有任何理由在类定义中声明私有函数?
替代方案本质上是pimpl惯用语,但没有多余的间接性。
这种语言的功能不仅仅是历史错误吗?
Answers:
私有成员函数可能是virtual
,并且在C ++(使用vtable)的常见实现中,类的所有客户端都必须知道虚拟函数的特定顺序和数量。即使一个或多个虚拟成员函数为,这也适用private
。
似乎这就像“把车摆在马前”,因为编译器实现的选择不应影响语言规范。但是,实际上,C ++语言本身是与使用vtables 的有效实现(Cfront)同时开发的。
如果允许将方法添加到其定义之外的类中,则任何人都可以在任何文件中的任何位置添加它们。
这将立即使所有客户端代码都能轻松访问私有和受保护的数据成员。
一旦完成了类定义,就无法将某些文件标记为由作者特别祝福以扩展它-只有平面翻译单元。因此,告诉编译器一组特定的方法是官方的,或者由类创建者认可的唯一合理方法是在类内部声明它们。
请注意,我们可以直接访问C ++中的内存,这意味着创建具有与您的类相同的内存布局的影子类型,添加我自己的方法(或只是将所有数据公开)和,通常是不容易的reinterpret_cast
。或者,我可以找到您的私有函数的代码,或者反汇编它。或者在符号表中查找函数地址并直接调用或。
这些访问说明符不会尝试阻止这些攻击,因为这是不可能的。它们仅指示应该如何使用类。
接受的答案针对虚拟专用功能对此进行了解释,但这仅回答了问题的一个特定方面,这比OP要求的要多得多。因此,我们需要改写:为什么要求在标头中声明非虚拟私有函数?
另一个答案援引一个事实,即必须在一个块中声明类-在此之后它们将被密封并且不能添加到其中。通过省略标题中的私有方法,然后尝试在其他位置定义私有方法,您将可以做到这一点。好点。为什么该类的某些用户能够以其他用户无法观察到的方式扩展它?私有方法是其中的一部分,并不被排除在外。但是你问为什么它们包括在内,似乎有点重言。为什么班级用户必须了解他们?如果它们不可见,则用户将无法添加任何内容,请记住。
因此,我想提供一个答案,而不是默认情况下仅包含私有方法,而是提供一个特定的点,以使它们对用户可见。赫伯·萨特(Herb Sutter)的GotW#100(关于Pimpl惯用语)中给出了需要公开声明的非虚拟私有功能的机械原因,作为其基本原理的一部分。我不会在这里继续介绍Pimpl,因为我敢肯定我们都知道。但这是相关的一点:
在C ++中,当头文件类定义中的任何内容发生更改时,都必须重新编译该类的所有用户-即使唯一的更改是该类用户甚至无法访问的私有类成员。这是因为C ++的构建模型基于文本包含,并且因为C ++假定调用者知道有关类的两个主要问题,而这两个类可能会受到私有成员的影响:
- 大小和布局:[成员和虚拟函数的名称-不言自明,性能出色,但我们为什么不在这里?
- 函数:调用代码必须能够解析对类成员函数的调用,包括无法访问的私有函数,这些私有函数会用非私有函数重载-如果私有函数更好地匹配,则调用代码将无法编译。(出于安全原因,C ++做出了谨慎的设计决策,在进行可访问性检查之前执行了重载解析。例如,人们认为将功能的可访问性从私有更改为公共不应改变合法调用代码的含义。)
作为委员会成员,萨特当然是一个非常可靠的消息来源,因此他一看到萨特就知道“一个故意的设计决定”。要求公开声明私有方法作为避免以后更改语义或意外破坏可访问性的一种方法可能是最令人信服的理由。值得庆幸的是,在此之前,整个事情似乎毫无意义!
这样做有两个原因。
首先,要意识到访问说明符是针对编译器的,与运行时无关。访问范围之外的私有成员是编译错误。
考虑一个短的函数,一行或两行。它的存在是为了减少代码在其他地方的复制,这还具有能够更改算法或其他任何东西在一个地方而不是在很多地方工作的优点(例如,更改排序算法)。
您是希望在标题中使用快速的一两行,还是在其中放置函数原型并在某处实现?在标题中更容易找到,而对于简短函数,拥有单独的实现则更为冗长。
还有另一个主要优势,那就是...
私有函数可以被内联,这必然要求它在头文件中。考虑一下:
class A {
private:
inline void myPrivateFunction() {
...
}
public:
inline void somePublicFunction() {
myPrivateFunction();
...
}
};
私有功能可以与公共功能一起内联。这是由编译器决定的,因为inline
关键字在技术上是一个建议,而不是一个要求。
inline
,没有理由在那里使用关键字。
.cpp
文件中可能有一个非成员函数,该函数由在类定义之外定义的成员函数内联,但是这样的函数不是私有的。
在头文件中拥有私有方法的另一个原因:在某些情况下,公共内联方法只不过是调用一个或多个私有方法而已。标头中包含私有方法意味着可以将对public方法的调用完全内联到私有方法的实际代码中,并且内联不会随着对私有方法的调用而停止。即使是从不同的编译单元(并且通常会从不同的编译单元调用公共方法)。
当然,还有一个原因是,如果编译器不知道所有方法(包括私有方法),就无法检测到重载解决方案的问题。
允许这些功能访问私有成员。否则,friend
无论如何您都需要在标题中添加它们。
如果任何函数可以访问该类的私有成员,那么私有将毫无用处。