The primary reason this is needed is that any code that uses a class needs to know about private class members in order to generate code that can handle it.
Consider the following class:
//foo.h
class foo {
char private_member[0x100];
public:
void do_something();
};
which is used by the following code:
#include "foo.h"
void f() {
foo x;
x.do_something();
}
If we compile this code on 32-bit Linux using gcc, with some flags to simplify the analysis, the function f
compiles to (with comments):
;allocate 256 bytes on the stack for a foo, plus 4 bytes for a foo*
0: 81 ec 04 01 00 00 sub esp,0x104
;the trivial constructor for foo is elided here
;compute the address of x
6: 8d 44 24 04 lea eax,[esp+0x4]
;pass the foo* to the function being called (the implicit first argument, this)
a: 89 04 24 mov DWORD PTR [esp],eax
;call x.do_something()
d: e8 fc ff ff ff call e <_Z1fv+0xe>
;deallocate the stack space used for this function
12: 81 c4 04 01 00 00 add esp,0x104
;return
18: c3 ret
There are two things of note here:
- The code for f() needs to know
sizeof(foo)
in order to allocate the correct amount of space for it.
- There is no call to foo's constructor. This is because the constructor is trivial, but it is impossible to know if foo has a trivial constructor without knowing its private class members.
Essentially, while the programmer does not need to know about the implementation of a class in order to use it, the compiler does. The C++ designers could have allowed private class members to be unknown to client code by introducing some levels of indirection, but that would have serious performance implications in some cases. Instead, the programmer can decide to implement this indirection themselves (via the pImpl idiom, for instance) if they decide the trade-off is worth it.