为什么 C++ 中的类必须声明它们的私有函数?它有实际的技术原因(它在编译时的作用是什么)还是仅仅是为了一致性?
7 回答
我问为什么必须声明私有函数,因为它们没有添加任何东西(既不是对象大小也不是 vtable 条目)让其他翻译单元知道
如果您考虑一下,这类似于static
在文件中声明某些函数。它从外部看不到,但它对编译器本身很重要。编译器想知道函数的签名,然后才能使用它。这就是你首先声明函数的原因。请记住,C++ 编译器是一次性的,这意味着在使用之前必须声明所有内容。1
从程序员的角度来看,声明私有函数仍然不是完全没用的。想象一下 2 个班级,其中之一是另一个班级friend
。朋友分区的第2类需要知道该类的私有信息(这个讨论变得很奇怪),否则他们无法使用它。
至于为什么 C++ 是这样设计的,我首先要说有历史原因:你不能在 C 中对结构进行切片,被 C++ 采用,所以你不能对类进行切片(并且被其他语言也从 C++ 分支)。我也猜想它是关于简单性:想象一下设计一种编译方法是多么困难,在这种方法中你可以将类拆分到不同的头文件中,让你的源文件知道它,并防止其他人向你添加东西班级。
最后一点是,private
函数会影响 vtable 的大小。也就是说,如果它们是virtual
.
1 其实不完全。如果类中有内联函数,它们可以引用稍后在同一类中定义的函数。但可能这个想法是从单次传递开始的,后来又添加了这个异常。
2 特别是内联成员函数。
您必须在类本身的定义中声明所有成员,以便编译器知道哪些函数可以成为成员。否则,第二个程序员可能(不小心?)出现并添加成员、犯错误并违反对象的保证,从而导致未定义的行为和/或随机崩溃。
有多种担忧,但是:
- C++ 不允许您在初始定义后重新打开一个类以在其中声明新成员。
- C++ 不允许您在组合形成程序的不同翻译单元中对类有不同的定义。
所以:
- .cpp 文件想要在类中声明的任何私有成员函数都需要在 .h 文件中定义,该类的每个用户也可以看到该文件。
从实际二进制兼容性的 POV 来看:正如 David 在评论中所说,私有virtual
函数会影响此类的 vtable 的大小和布局以及任何使用它作为基础的类。因此,即使在编译无法调用它们的代码时,编译器也需要了解它们。
C++ 是否可以以不同的方式发明,以允许 .cpp 文件重新打开类并添加某些类型的附加成员函数,并需要实现以确保这不会破坏二进制兼容性?是否可以放宽一个定义规则,以允许在某些方面有所不同的定义?例如,静态成员函数和非虚非静态成员函数。
可能对两者都是肯定的。我不认为存在任何技术障碍,尽管当前的 ODR对于定义“不同”的原因非常严格(因此在允许外观非常相似的定义之间存在二进制不兼容性方面非常慷慨)。我认为在规则中引入这种例外情况的案文会很复杂。
最终它可能归结为“设计师想要那样”,或者可能是有人尝试过它并遇到了我没有想到的障碍。
访问级别不影响可见性。私有函数对外部代码可见,并且可以通过重载决议来选择(这会导致访问冲突错误):
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();
}
原因之一是在 C++ 中朋友可以访问您的私人信息。为了让朋友访问它们,朋友必须了解它们。
类的私有成员仍然是该类的成员,因此必须声明它们,因为其他公共成员的实现可能依赖于该私有方法。声明它们将允许编译器将对该函数的调用理解为成员函数调用。
如果您有一个仅在.cpp
文件中使用且不依赖于对类的其他私有成员的直接访问的方法,请考虑将其移至匿名命名空间。然后,它不需要在头文件中声明。
为什么必须声明私有函数有几个原因。
首次编译时错误检查
访问修饰符的目的是在编译时捕获某些类(没有双关语)的编程错误。私有函数是这样的函数,如果有人从类外部调用它们,那将是一个错误,您希望尽早了解它。
二次铸造与继承
取自 C++ 标准:
3 [注意:私有基类的成员可能无法作为继承的成员名称访问,但可以直接访问。由于指针转换 (4.10) 和显式转换 (5.4) 的规则,如果使用隐式转换,则从派生类的指针到指向不可访问的基类的指针的转换可能是格式错误的,但格式正确如果使用显式强制转换。
第三个朋友
朋友们在那里私下互相展示。私有方法可以被另一个朋友类调用。
第 4 次一般理智和良好设计
曾经与另外 100 名开发人员一起参与过一个项目。拥有一套标准和通用的规则有助于保持可维护性。声明私有的东西对组中的其他人具有特定的含义。
这也流入了良好的 OO 设计原则。什么可以公开,什么不可以